Problem/Motivation

A start queues its token from inside the transaction that creates the run. That is deliberate: the instance, its variables and its start token are written atomically, so a throw partway cannot leave a committed running instance with no live token.

The database queue, however, does not create its table when a module is installed. It creates it the first time anything is queued: DatabaseQueue::createItem() catches the missing table and runs CREATE TABLE. So on a site where nothing has been queued yet, the first start runs that CREATE TABLE inside its own open transaction.

MySQL commits an open transaction implicitly when it sees DDL. The transaction the start opened is therefore already finished by the time the start goes to commit it, and the transaction stack is left with nothing under it.

A start on its own survives that. Its own frame was the only thing on the stack, so there is nothing left to unwind and nothing notices.

A start made while another start is still open does not survive it. The engine saves the instance it is creating before it announces it, and that save runs every module's entity insert hooks, so a module that starts a process of its own from one of those hooks puts a second start inside the first. The inner start runs the CREATE TABLE, the implicit commit voids the stack, and the outer start's commit then finds nothing:

Drupal\Core\Database\TransactionOutOfOrderException: Error attempting commit of ...\drupal_transaction. Active stack: *** empty ***

Both runs are written, and the caller is told the start failed.

SQLite does not commit on DDL, so none of this shows there. The kernel suite runs SQLite unless it is asked for MySQL, which is why it has not surfaced.

Steps to reproduce

Against MySQL, on a site whose queue table does not exist yet: implement hook_orchestra_instance_insert() in a module and start a second workflow from it, guarded so it fires once and not for its own instance. Start the first workflow, and it throws. Create the queue table first, by queueing anything at all, and the same code passes.

Proposed resolution

None. Closing this as won't fix: the fix costs more than the fault.

Reaching it needs two things at once. The queue must have no table yet, which is true only on a site where nothing at all has ever been queued, so in practice the minutes after an install. And a start must happen while another start is still open, which needs a module to start a process of its own from an instance insert hook; nothing here does that. The result is one thrown start, after which the table exists and it cannot happen again.

Every way of preventing it costs an install hook, an update for every site that already has Orchestra, and a test module whose only purpose is to reproduce it. Creating the table outright also means this module creating a table it does not own: nothing else in core or contrib does that, the queue's table being core's to make when something first queues into it.

The behaviour itself is not Orchestra's. A database queue creates its table on first use, and on MySQL that DDL commits whatever transaction is open, so any module that queues from inside one is exposed the same way. Recorded here because the mechanism is worth knowing if a stranger case ever turns up.

Remaining tasks

None.

AI-Generated: Yes (Claude Code was used to help investigate this and to draft the issue summary, and to write the fix and its test on the merge request. I reviewed the work, and the new test was confirmed to fail against unpatched 1.x on MySQL and to pass with the change.)

Issue fork orchestra-3622732

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review
mably’s picture

Title: A start made inside another start's transaction throws TransactionOutOfOrderException on MySQL » Settle the queue's table before a start opens the transaction that queues into it
Issue summary: View changes
mably’s picture

Title: Settle the queue's table before a start opens the transaction that queues into it » Give the advance queue its table at install, so no start has to create one
Issue summary: View changes
mably’s picture

Issue summary: View changes
Status: Needs review » Closed (won't fix)

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.