Skip to content

Mark CBCsrf as threadSafe to avoid handing out a half-wired singleton - #16

Merged
lmajano merged 1 commit into
coldbox-modules:developmentfrom
davidsf:threadsafe-singleton
Sep 10, 2026
Merged

Mark CBCsrf as threadSafe to avoid handing out a half-wired singleton#16
lmajano merged 1 commit into
coldbox-modules:developmentfrom
davidsf:threadsafe-singleton

Conversation

@davidsf

@davidsf davidsf commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Problem

csrfToken() intermittently throws variable [CACHESTORAGE] doesn't exist at models/cbcsrf.cfc when several requests run concurrently against a cold singleton cache — in our case the first requests after every framework reinit on deploy.

Singleton.getFromScope() publishes the instance to the singleton cache before autowiring it (so circular dependencies can resolve), and the first containsKey() check runs outside the lock:

if ( NOT variables.singletons.containsKey( cacheKey ) ) {      // unlocked check
  lock ... {
    if ( NOT variables.singletons.containsKey( cacheKey ) ) {
      var tmpSingleton = variables.injector.buildInstance( ... );
      if ( NOT arguments.mapping.getThreadSafe() ) {
        variables.singletons.put( cacheKey, tmpSingleton );     // published here
      }
      variables.injector.autowire( target = tmpSingleton, ... ); // injected after

A thread that passes the unlocked check between the put() and the end of autowire() gets the instance without variables.cacheStorage.

Fix

Annotate the component as threadSafe: WireBox then stores it only after autowiring, and other threads wait on the lock. CacheStorage in cbstorages already carries the annotation for the same reason.

CBCsrf has no circular dependencies (settings comes from the coldbox:moduleSettings DSL, cacheStorage from cacheStorage@cbcsrf), so nothing relies on the early publication.

Changelog entry added under Unreleased.

Without the annotation WireBox stores the singleton in its cache before
autowiring it, and the first containsKey() check in
coldbox/system/ioc/scopes/Singleton.cfc runs outside the lock. A concurrent
request can therefore get the instance before cacheStorage is injected and
fail with "variable [CACHESTORAGE] doesn't exist".

CacheStorage@cbstorages already carries the annotation for the same reason.
CBCsrf has no circular dependencies, so publishing after autowire is safe.
@lmajano
lmajano merged commit 367738d into coldbox-modules:development Sep 10, 2026
12 checks passed
@lmajano

lmajano commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Gracias amigo

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.

2 participants