{"record":{"id":"2d0d688ebad09800","repo":"louthy/language-ext","slug":"refs-can-only-commute-from-within-a-transaction","errorCode":null,"errorMessage":"Refs can only commute from within a transaction","messagePattern":"Refs can only commute from within a transaction","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"LanguageExt.Core/Concurrency/STM/STM.cs","lineNumber":431,"sourceCode":"    ///     \n    /// and returns the in-transaction-value when complete.\n    /// \n    /// At the commit point of the transaction, `f` is run *AGAIN* with the\n    /// most recently committed value:\n    /// \n    ///     `f(most-recently-committed-value-of-ref)`\n    /// \n    /// Thus `f` should be commutative, or, failing that, you must accept\n    /// last-one-in-wins behavior.\n    /// \n    /// Commute allows for more concurrency than just setting the items\n    /// </summary>\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    internal static A Commute<A>(long id, Func<A, A> f)\n    {\n        if (transaction.Value == null)\n        {\n            throw new InvalidOperationException(\"Refs can only commute from within a transaction\");\n        }\n        return (A)transaction.Value.Commute(id, CastCommute(f));\n    }\n\n    /// <summary>\n    /// Must be called in a transaction. Sets the in-transaction-value of\n    /// ref to:  \n    /// \n    ///     `f(in-transaction-value-of-ref)`\n    ///     \n    /// and returns the in-transaction-value when complete.\n    /// \n    /// At the commit point of the transaction, `f` is run *AGAIN* with the\n    /// most recently committed value:\n    /// \n    ///     `f(most-recently-committed-value-of-ref)`\n    /// \n    /// Thus `f` should be commutative, or, failing that, you must accept","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Concurrency/STM/STM.cs#L413-L449","documentation":"`STM.Commute` registers a commutative operation on a Ref (one whose effect is independent of ordering). Like all ref operations, it requires an active STM transaction; `STM.Commute` checks the ambient transaction and throws `InvalidOperationException` when none is running. Commute entries are buffered and applied at commit time, so a transaction must exist.","triggerScenarios":"Calling `ref.Commute(f)` (or `STM.Commute` indirectly via a ref's commute method) outside of an `STM.sync`/`atomic` block — e.g. from plain code, a timer callback, or after the sync delegate has already returned.","commonSituations":"Attempting to enqueue commutative updates from background tasks outside the transaction; forgetting that commute, like read/write, is transaction-only; porting Clojure `commute` code and omitting the enclosing `dosync`/`sync`.","solutions":["Move the commute call inside a transaction: `STM.sync(() => { ref.Commute(f); return unit; });`","If the update doesn't need ordering-independence, use a regular in-transaction write (`ref.Value = ...`) inside `sync` instead.","Verify the call isn't escaping the sync delegate via a captured lambda that runs later (e.g. inside Task.Run within sync)."],"exampleFix":"// before\nrefValue.Commute(x => x + 1); // throws outside transaction\n\n// after\nSTM.sync(() => { refValue.Commute(x => x + 1); return unit; });","handlingStrategy":"try-catch","validationCode":"// Only call Commute from within the sync delegate; assert ambient transaction there.\nDebug.Assert(inSync, \"Commute requires STM.sync\");","typeGuard":null,"tryCatchPattern":"try { STM.sync(() => { r.Commute(f); return unit; }); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"commute\")) { /* handle */ }","preventionTips":["Group all commute/read/write calls together inside one sync block.","Never schedule commute calls via Task.Run or timers outside the transaction.","Document commute methods as transaction-only in your API surface."],"tags":["stm","concurrency","transaction","csharp"],"backgroundTag":"invalid-state-transition","analyzedSha":"2f0e3628242889774d4141960a35671a0280051f","analyzedAt":"2026-09-15T03:31:55.716Z","contentChangedAt":"2026-09-15T03:31:55.716Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}