From 12e45b843432de4c58e6dc3662d4478a4f0ed014 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Henrik=20Grubbstr=C3=B6m=20=28Grubba=29?=
 <grubba@grubba.org>
Date: Mon, 26 Oct 2020 11:52:21 +0100
Subject: [PATCH] Concurrent.Promise: Fixed race-condition in finalise().

The API for finalise() raced with on_{success,failure}(), as
new callbacks could get registered after finalise() was called,
but before the state was updated.

Fixes #10055.
---
 lib/modules/Concurrent.pmod | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/lib/modules/Concurrent.pmod b/lib/modules/Concurrent.pmod
index 6304cbb11b..c5fb0362c1 100644
--- a/lib/modules/Concurrent.pmod
+++ b/lib/modules/Concurrent.pmod
@@ -232,6 +232,7 @@ class Future
   //!   @[on_failure()], @[query_success_callbacks()]
   this_program on_success(function(mixed, mixed ... : void) cb, mixed ... extra)
   {
+    Thread.MutexKey key = mux->lock();
     switch (state) {
       case STATE_FULFILLED:
         call_callback(cb, result, @extra);
@@ -274,6 +275,7 @@ class Future
   //!   @[on_success()], @[query_failure_callbacks()]
   this_program on_failure(function(mixed, mixed ... : void) cb, mixed ... extra)
   {
+    Thread.MutexKey key = mux->lock();
     switch (state) {
       case STATE_REJECTED:
 	state = STATE_REJECTION_REPORTED;
@@ -941,14 +943,19 @@ class Promise
     return Future::this;
   }
 
-  protected this_program finalise(State newstate, mixed value, int try,
-    array(array(function(mixed, mixed ...: void)|array(mixed))) cbs)
+  protected this_program finalise(State newstate, mixed value, int try)
   {
     Thread.MutexKey key = mux->lock();
     if (state <= STATE_PENDING)
     {
       state = newstate;
       result = value;
+      array(array(function(mixed, mixed ...: void)|array(mixed))) cbs;
+      if (state == STATE_FULFILLED) {
+	cbs = success_cbs;
+      } else {
+	cbs = failure_cbs;
+      }
       key = 0;
       cond->broadcast();
       if (sizeof(cbs))
@@ -989,7 +996,7 @@ class Promise
   //!   @[try_success()], @[try_failure()], @[failure()], @[on_success()]
   this_program success(mixed value, void|int try)
   {
-    return finalise(STATE_FULFILLED, value, try, success_cbs);
+    return finalise(STATE_FULFILLED, value, try);
   }
 
   //! Fulfill the @[Future] if it hasn't been fulfilled or failed already.
@@ -1027,7 +1034,7 @@ class Promise
   this_program failure(mixed value, void|int try)
   {
     return
-     finalise(STATE_REJECTED, value, try, failure_cbs);
+     finalise(STATE_REJECTED, value, try);
   }
 
   //! Maybe reject the @[Future] value.
-- 
GitLab