Documentation
¶
Overview ¶
gvlscan reports calls that BLOCK the operating-system thread but are not wrapped in a call that releases the interpreter lock first.
It exists because grep cannot answer the question. In a pure-Go Ruby VM the dangerous call and the harmless one are spelled identically:
conn.Read(buf) // net.Conn -- blocks until a peer sends, maybe forever buf.Read(p) // *bytes.Reader -- returns immediately, always
Both are `.Read(`. Telling them apart needs the type of the receiver, so this loads real type information rather than matching text. That difference is not academic: go-embedded-ruby#771 was an unbounded hang -- a Ruby program that talked to a server in one of its own threads never returned, and the embedder calling ruby.Run never got control back -- caused by exactly one unwrapped net.Conn read in a file that otherwise wrapped all of them.
WHAT IT CAN AND CANNOT SEE. Enclosure is LEXICAL: a blocking call counts as wrapped when a wrapper call encloses it in the same function body, which is the shape these wrappers are used in:
ioBlock(vm, func() { n, err = conn.Read(buf) })
A blocking call sitting in a helper that is only ever reached from inside a wrapper is reported anyway. That is a false positive, and the tool says so by printing the enclosing function, so the reader can follow the callers. It is the safe direction to be wrong in: the other one would be silence about a real hang. -callers prints, for each function holding an unwrapped call, the places that call it, which is usually enough to settle it without reading the file.
Exit status: 0 when nothing is unwrapped, 1 when something is, 2 when the packages could not be loaded -- which is a refusal to answer, not an answer.