Skip to content

An Exit reaches the evaluation it belongs to

Choose a tag to compare

@pisarev pisarev released this 06 Aug 10:13
· 14 commits to main since this release

A formula can call out to a second parser, and that parser can call back into
the first. The frame chain of one thread then reads A, B, A, and an exit
raised in the innermost A has to travel past B untouched.

Fixed: a parser standing in the way took the Exit. Version 1.0.2 asked one
question in the handler - "is there another frame of MY parser below me?" - and
for the middle B the answer was no. B took itself for the root, swallowed an
exit addressed to A and returned its value as its own. The outer A never saw
it and quietly finished a different sum: 49 where 42 was due. The exception now
carries its owner, so the handler asks two questions: is this exception mine,
and am I the outermost evaluation of my parser. The second alone was never
enough.

The defect was not a regression in 1.0.2 - the code before that release behaved
the same way. We fixed the ownership of exit and closed two cases out of
three.

Cheaper, too. Looking for the enclosing evaluation moved off the path of an
ordinary formula: it happens only when an exception actually appears.

Fixed: the README promised more than the code delivers. "Evaluation is
thread-safe after that" was not true. A TScript is a mutable execution image,
not immutable bytecode: evaluation writes intermediate values and the result
into the script headers. So one buffer cannot carry two active evaluations -
not in two threads, and not reentrantly in one. Measured: a formula that
re-enters the same buffer answers 302 where 301 is due; four threads on one
script instance answer wrongly several thousand times out of four thousand
each, without raising anything.

The Thread safety section is written from scratch: what to freeze before
starting workers, why Copy(Script) is required and how it differs from a plain
assignment, what happens to registered variables and callbacks, and how long the
returned PValue stays valid. The copy rule comes with a working example,
samples/docs/threadsafe.dpr, which the build matrix compiles and runs.

Proof. tests/ExitRoutingTest.dpr - 32 checks covering recursion, a chain
through one foreign parser, a chain through two, an exit owned by the parser
in the middle, the legacy constructor inside and outside an evaluation, the
reentrancy that is allowed, and the semantics of the formula-level TryExcept
and TryFinally. On 1.0.2 the suite fails 7 of 31; here it is green, on Delphi
13 and FPC 3.3.1 alike.

tests/SharedBufferRepro.dpr reproduces the forbidden case on purpose. It is
kept out of the green suite: it demonstrates what the documentation now
prohibits, and it will be the starting measurement on the day the execution
image becomes read-only.

Packaging. The Lazarus package descriptions said "Copyright Yuriy Pisarev"
where the repository is MIT, and carried a version unrelated to the product.
Both are corrected in all package files.