Skip to content

1.18.2

Choose a tag to compare

@freitasjca freitasjca released this 18 Sep 10:15
· 28 commits to main since this release

Fixed

FIX-LOADRACE-1 — the FFI library loaders were not thread-safe.

NghttpLoad and NghttpsslLoad were if GLoaded then Exit(True) followed, an
entire library load later, by GLoaded := True — a check-then-set with no lock
between the two. Two threads arriving together both saw False and both ran the
whole sequence: the library opened twice, ~50 function pointers rewritten twice,
and two handles acquired where the unload path only ever releases one.

The OpenSSL loader was worse than no guard at all. Its Boolean re-entry
flag turned a second thread arriving mid-load into Exit(False), surfaced to the
caller as "OpenSSL could not be loaded" — while the load actually succeeded
moments later on the other thread. A spurious and thoroughly misleading failure,
on the one path whose job is to explain why TLS is unavailable.

Both loaders now use a TCriticalSection, as every other unit in src/ that
shares state across threads already did. Both Unload paths take the same lock;
they clear the loaded flag and release handles that a thread inside a load is
actively relying on.

Who was affected

Anyone constructing TNghttp2Client, TNghttp2Server, TTlsClientContext or
TTlsServerContext from more than one thread at startup — those four
constructors are what reach the loaders. A process that creates its first client,
server or TLS context on a single thread was never exposed, because by the time
any other thread arrives the load has completed and the fast return is correct.

Upgrading

No API change. No behaviour change for single-threaded initialisation.
TTlsClientContext.EnableHttp2Alpn and the loader entry points keep their
signatures and their semantics.

Honest limits of the verification

Neither test suite races first-load across threads, so neither can demonstrate
this fix
. What they establish is that both units still compile on both
toolchains and that nothing regressed: FPC 3.3.1 at 37 passed / 0 failed / 1
skipped, Delphi 12 at ALL STAGES PASSED.

A gate that could observe it is cheap and is the intended follow-up: N threads
calling NghttpsslLoad simultaneously in a fresh process, asserting every one
returns True — because before this change, the losers of that race got a
spurious False.