diff --git a/whtree/modules/system/whlibs/internal/jobs.whlib b/whtree/modules/system/whlibs/internal/jobs.whlib
index 9b7dc098ad..2060ff0521 100644
--- a/whtree/modules/system/whlibs/internal/jobs.whlib
+++ b/whtree/modules/system/whlibs/internal/jobs.whlib
@@ -90,7 +90,17 @@ OBJECTTYPE MessageWaiter EXTEND __HS_INTERNAL_AsyncCallbackWaitHelperBase
UPDATE BOOLEAN FUNCTION HandleCallback(INTEGER handle, RECORD context)
{
- RECORD rec := this->link->ReceiveMessage(DEFAULT DATETIME);
+ RECORD rec;
+ TRY
+ rec := this->link->ReceiveMessage(DEFAULT DATETIME);
+ CATCH(OBJECT e)
+ {
+ // autothrow and closed-link errors belong to the pending promise, not
+ // the unrelated caller pumping the event loop that delivered this read.
+ this->link->asyncmessagewaiter := DEFAULT OBJECT;
+ this->defer.reject(e);
+ RETURN TRUE;
+ }
IF (rec.status != "timeout" OR handle = -1)
{
IF (this->replyto != 0 AND rec.status = "ok" AND rec.replyto != this->replyto)
diff --git a/whtree/modules/webhare_testsuite/tests/system/nodejs/test_async_ipc.whscr b/whtree/modules/webhare_testsuite/tests/system/nodejs/test_async_ipc.whscr
new file mode 100644
index 0000000000..99c34b2a21
--- /dev/null
+++ b/whtree/modules/webhare_testsuite/tests/system/nodejs/test_async_ipc.whscr
@@ -0,0 +1,46 @@
+Accept(DEFAULT DATETIME);
+ client->autothrow := TRUE;
+ TRY
+ {
+ OBJECT request := client->AsyncDoRequest([value := 1]);
+ RECORD incoming := server->ReceiveMessage(MAX_DATETIME);
+ server->SendExceptionReply(NEW Exception("synthetic IPC rejection"), incoming.msgid);
+ VARIANT settled := AWAIT CreatePromiseAllSettled(OBJECT[request, CreateResolvedPromise(42)]);
+ TestEq("rejected", settled[0].status);
+ TestEq("synthetic IPC rejection", settled[0].reason->what);
+ TestEq("fulfilled", settled[1].status);
+ TestEq(42, settled[1].value);
+
+ // The failed read must release its registration so this link is reusable.
+ request := client->AsyncDoRequest([value := 2]);
+ incoming := server->ReceiveMessage(MAX_DATETIME);
+ server->SendReply([value := 2], incoming.msgid);
+ RECORD reply := AWAIT request;
+ TestEq(2, reply.msg.value);
+
+ request := client->AsyncReceiveMessage(MAX_DATETIME);
+ server->SendExceptionReply(NEW Exception("synthetic async receive rejection"), 0);
+ settled := AWAIT CreatePromiseAllSettled(OBJECT[request]);
+ TestEq("rejected", settled[0].status);
+ TestEq("synthetic async receive rejection", settled[0].reason->what);
+ }
+ FINALLY
+ {
+ client->Close();
+ server->Close();
+ port->Close();
+ }
+}
+
+RunTestframework([PTR TestAsyncIPCRejectsAutothrownExceptions]);
diff --git a/whtree/modules/webhare_testsuite/tests/system/nodejs/testinfo.xml b/whtree/modules/webhare_testsuite/tests/system/nodejs/testinfo.xml
index 24954b9f26..22a071e591 100644
--- a/whtree/modules/webhare_testsuite/tests/system/nodejs/testinfo.xml
+++ b/whtree/modules/webhare_testsuite/tests/system/nodejs/testinfo.xml
@@ -45,6 +45,7 @@
+