concurrency

Concurrency and thread-safety

ORM::Factory's registry, sequences, and build pipeline are designed for concurrent use. The guarantees here are explicit; assumptions outside this page are not promises.

What is thread-safe

  • Reads of the factory registry (factory-by-name, factories, aliases, factory-names, variant-names-for, etc.) are safe from any thread, including while define is running on another thread.

  • Sequence generation (generate, generate-list, inline sequence attributes invoked during a build) is atomic. Every value emitted by a given sequence is unique and ordered consistently with calls.

  • rewind-sequences is safe to call concurrently with generate (a rewind racing a generate is well-defined: either the generate sees the pre-rewind counter or the post-rewind one — never a corrupted state).

  • build / create / build-stubbed / attributes-for across threads produce independent results. The per-build evaluator is created fresh for each invocation, and the build chain used for cycle detection is thread-local via a dynamic variable.

What is not thread-safe

  • Concurrent define and modify calls serialise behind a single registry lock, but they do not coordinate semantically: if two threads each try to define a factory named 'user', the first wins and the second raises X::ORM::Factory::DuplicateFactory. Treat definition as a one-time setup phase.

  • The detected persistence adapter (ORM::Factory.persistence) caches its result across the process. Setting an adapter from multiple threads at startup races; pick one thread (typically main) to call set-persistence / reset-persistence.

  • The adapter itself is not necessarily thread-safe — the ORM::ActiveRecord adapter shares a single connection process-wide and inherits AR's concurrency model.

Recommended pattern

The intended model is definition at startup, builds concurrently:

# Main thread, before any workers spin up
define {
  .sequence: 'serial';
  .factory: 'user', {
    .fname:  'Greg';
    .serial: { ORM::Factory.generate('serial') };
  };
};

# Workers can now build concurrently
my @users = (^100).hyper(:degree(8)).map({
  ORM::Factory.build('user');
}).list;

@users.map(*.serial).unique.elems == 100;   # always

Verifying it

The specs/factory/concurrency-spec.raku suite exercises the guarantees above with hypered map blocks. If you change the registry, sequence, or build code, run that spec across all three adapters before merging.

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.