Skip to content

Hand a lua script copies in both directions - #657

Merged
folbricht merged 1 commit into
masterfrom
lua-answer-ownership
Sep 27, 2026
Merged

folbricht merged 1 commit into
masterfrom
lua-answer-ownership

Conversation

@folbricht

Copy link
Copy Markdown
Owner

A script keeps its interpreter for the life of the process, so it can hold on to anything it is given, in a global, an upvalue or a table. Resolve hands the instance back to the pool as it returns, while the caller has only just been given the answer and still has to truncate, pad and pack it. A script that kept the answer, a cache of the last reply being the obvious way to write that, could therefore write to it from the next query to reach that instance, at the same time as the listener was reading it:

WARNING: DATA RACE
Write at 0x00c0002741c8 by goroutine 33:
  RegisterMessageType.func3        the message field setter, from a later query
  (*Lua).Resolve()
Previous read at 0x00c0002741c8 by goroutine 32:
  Pack, on the answer the earlier caller was given

The answer is copied on the way out, as the query already was on the way in, so nothing crosses the boundary by reference in either direction. Msg.Copy is deep enough to sever it: the address bytes of an A record, the strings of a TXT and the OPT record all come out independent.

Globals need no fix

Each instance is a separate interpreter with its own globals table, and the compiled script the instances share is only ever read. One instance counting queries in a global reaches 3 while another still reports 1, and sixteen goroutines over four instances writing globals and tables throughout run clean under the race detector.

What they are is worth writing down, which the guide now does: a global is kept between queries but only on the instance that set it, so with the default of four a script counting queries counts about a quarter of them in each, and which instance a query lands on is whichever is free. The guide also says what the copying means for a script that holds on to a query or an answer.

Tests

One that a later query cannot reach the answer an earlier caller holds, which fails without the copy, and one that a script returning no answer still returns none.

A script keeps its interpreter for the life of the process, so it can hold on
to anything it is given, in a global, an upvalue or a table. Resolve hands the
instance back to the pool as it returns, while the caller has only just been
given the answer and still has to truncate, pad and pack it. A script that kept
the answer, a cache of the last reply being the obvious way to write that, could
therefore write to it from the next query to reach that instance, at the same
time as the listener was reading it. The race detector reports it on the
message field setter against Pack.

The answer is copied on the way out, as the query already was on the way in, so
nothing crosses the boundary by reference in either direction.

Globals are not the problem and need no fix: each instance is a separate
interpreter with its own globals table, and the compiled script the instances
share is only ever read. One instance counting queries in a global reaches 3
while another still reports 1, and sixteen goroutines over four instances
writing globals and tables throughout run clean under the race detector. What
they are is surprising, since a global is kept between queries but only on the
instance that set it, and with four instances a script counting queries counts
about a quarter of them in each. The scripting guide says so now, along with
what copying means for a script that holds on to a query or an answer.
@folbricht
folbricht merged commit 9f96dab into master Sep 27, 2026
16 checks passed
@folbricht
folbricht deleted the lua-answer-ownership branch September 27, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant