diff --git a/src/js.c b/src/js.c index b84945c7e..65b57440a 100644 --- a/src/js.c +++ b/src/js.c @@ -1815,7 +1815,7 @@ js_checkFetchedMsg(natsSubscription *sub, natsMsg *msg, uint64_t fetchID, bool c } static natsStatus -_sendPullRequest(natsConnection *nc, const char *subj, const char *rply, +_publishPullRequest(natsConnection *nc, const char *subj, const char *rply, natsBuffer *buf, jsFetchRequest *req) { natsStatus s; @@ -1991,7 +1991,7 @@ _fetch(natsMsgList *list, natsSubscription *sub, jsFetchRequest *req, bool simpl req->Batch = req->Batch - count; req->Expires = NATS_MILLIS_TO_NANOS(timeout); req->NoWait = noWait; - s = _sendPullRequest(nc, subj, rply, &buf, req); + s = _publishPullRequest(nc, subj, rply, &buf, req); } IFOK(s, natsSub_nextMsg(&msg, sub, timeout, true)); if (s == NATS_OK) @@ -2909,6 +2909,7 @@ natsStatus js_maybeFetchMore(natsSubscription *sub, jsFetch *fetch) { jsFetchRequest req = {.Expires = 0}; + if (fetch->opts.NextHandler == NULL) return NATS_OK; @@ -2923,27 +2924,34 @@ js_maybeFetchMore(natsSubscription *sub, jsFetch *fetch) req.NoWait = fetch->opts.NoWait; req.Heartbeat = fetch->opts.Heartbeat * 1000 * 1000; // ns, go time.Duration + size_t replySubjectSize = 1 + strlen(sub->subject) + 20; + char *replySubject = NATS_MALLOC(replySubjectSize); + if (replySubject == NULL) + return nats_setDefaultError(NATS_NO_MEMORY); + char buffer[128]; natsBuffer buf; natsBuf_InitWithBackend(&buf, buffer, 0, sizeof(buffer)); nats_lockSubAndDispatcher(sub); - jsSub *jsi = sub->jsi; jsi->inFetch = true; jsi->fetchID++; - snprintf(fetch->replySubject, sizeof(fetch->replySubject), "%.*s%" PRIu64, + snprintf(replySubject, replySubjectSize, "%.*s%" PRIu64, (int)strlen(sub->subject) - 1, sub->subject, // exclude the last '*' jsi->fetchID); - natsStatus s = _sendPullRequest(sub->conn, jsi->nxtMsgSubj, fetch->replySubject, &buf, &req); + nats_unlockSubAndDispatcher(sub); + + natsStatus s = _publishPullRequest(sub->conn, jsi->nxtMsgSubj, replySubject, &buf, &req); if (s == NATS_OK) { + nats_lockSubAndDispatcher(sub); fetch->requestedMsgs += req.Batch; + nats_unlockSubAndDispatcher(sub); } - nats_unlockSubAndDispatcher(sub); - natsBuf_Destroy(&buf); + NATS_FREE(replySubject); return NATS_UPDATE_ERR_STACK(s); } @@ -3086,14 +3094,14 @@ js_PullSubscribeAsync(natsSubscription **newsub, jsCtx *js, const char *subject, natsTimer_Reset(jsi->hbTimer, dur); } + nats_unlockSubAndDispatcher(sub); + if (s == NATS_OK) { // Send the first fetch request. s = js_maybeFetchMore(sub, fetch); } - nats_unlockSubAndDispatcher(sub); - if (s != NATS_OK) { natsSubscription_Destroy(sub); diff --git a/src/natsp.h b/src/natsp.h index 80164238b..1a6cc1410 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -389,9 +389,6 @@ typedef struct __jsFetch // Timer for the fetch expiration. We leverage the existing jsi->hbTimer for // checking missed heartbeats. natsTimer *expiresTimer; - - // Matches jsi->fetchID - char replySubject[NATS_DEFAULT_INBOX_PRE_LEN + NUID_BUFFER_LEN + 32]; // big enough for {INBOX}.number } jsFetch; typedef struct __jsSub diff --git a/test/list_test.txt b/test/list_test.txt index a7a2218a1..8a2045581 100644 --- a/test/list_test.txt +++ b/test/list_test.txt @@ -77,6 +77,8 @@ _test(IPResolutionOrder) _test(IsClosed) _test(IsReconnectingAndStatus) _test(IsValidSubscriber) +_test(JetStream_GH823) +_test(JetStream_GH823CustomInboxPrefix) _test(JetStreamBackOffRedeliveries) _test(JetStreamContext) _test(JetStreamContextDomain) diff --git a/test/test.c b/test/test.c index 039971bd9..d55d33ec6 100644 --- a/test/test.c +++ b/test/test.c @@ -28932,6 +28932,20 @@ _recvPullAsync(natsConnection *nc, natsSubscription *sub, natsMsg *msg, natsMutex_Unlock(arg->m); } +static void +_recvPullAsyncCount(natsConnection *nc, natsSubscription *sub, natsMsg *msg, + void *closure) +{ + struct threadArg *arg = (struct threadArg *)closure; + + natsMutex_Lock(arg->m); + arg->sum++; + natsCondition_Signal(arg->c); + natsMutex_Unlock(arg->m); + + natsMsg_Destroy(msg); +} + static void _completePullAsync(natsConnection *nc, natsSubscription *sub, natsStatus exitStatus, void *closure) @@ -28994,6 +29008,181 @@ _testBatchCompleted(struct threadArg *args, natsSubscription *sub, natsStatus ex return result; } + +static bool _GH823_nextHandler(int *messages, int64_t *maxBytes, natsSubscription *sub, void *closure) +{ + *messages = 5; + return true; +} + +static int +_GH823_waitForMsgs(struct threadArg *arg, int count) +{ + natsStatus s = NATS_OK; + int sum; + + natsMutex_Lock(arg->m); + while ((s != NATS_TIMEOUT) && (arg->sum < count)) + s = natsCondition_TimedWait(arg->c, arg->m, 2000); + sum = arg->sum; + natsMutex_Unlock(arg->m); + + return sum; +} + +void test_JetStream_GH823(void) +{ + natsStatus s = NATS_OK; + jsErrCode jerr = 0; + jsStreamConfig sc; + struct threadArg args; + struct threadArg args2; + const int numMsgs = 5000; + natsConnection *ncSub = NULL; + jsCtx *jsSub = NULL; + + JS_SETUP(2, 9, 2); + + s = _createDefaultThreadArgsForCbTests(&args); + if (s == NATS_OK) + s = _createDefaultThreadArgsForCbTests(&args2); + if (s != NATS_OK) + FAIL("Unable to setup test"); + + test("Create stream for foo, bar: "); + jsStreamConfig_Init(&sc); + sc.Name = "TEST"; + sc.Subjects = (const char *[2]){"foo","bar"}; + sc.SubjectsLen = 2; + s = js_AddStream(NULL, js, &sc, NULL, &jerr); + testCond((s == NATS_OK) && (jerr == 0)); + + test("Publish thousands of test messages: "); + for (int i=0; i= 6); + + test("Create the second async pull subscriber and start receiving: "); + natsSubscription *sub2 = NULL; + s = js_PullSubscribeAsync(&sub2, jsSub, "bar", NULL, _recvPullAsyncCount, &args2, &so, NULL, &jerr); + testCond(s == NATS_OK); + + test("Second subscriber received a second batch: "); + testCond(_GH823_waitForMsgs(&args2, 6) >= 6); + + natsSubscription_Destroy(sub1); + natsSubscription_Destroy(sub2); + jsCtx_Destroy(jsSub); + natsConnection_Destroy(ncSub); + + JS_TEARDOWN + _destroyDefaultThreadArgs(&args); + _destroyDefaultThreadArgs(&args2); +} + +void test_JetStream_GH823CustomInboxPrefix(void) +{ + natsStatus s = NATS_OK; + jsErrCode jerr = 0; + jsStreamConfig sc; + struct threadArg args; + const int numMsgs = 10; + char prefix[97]; + natsOptions *subOpts = NULL; + natsConnection *ncSub = NULL; + jsCtx *jsSub = NULL; + natsSubscription *sub = NULL; + jsOptions so; + + JS_SETUP(2, 9, 2); + + s = _createDefaultThreadArgsForCbTests(&args); + if (s != NATS_OK) + FAIL("Unable to setup test"); + + test("Create stream for foo: "); + jsStreamConfig_Init(&sc); + sc.Name = "TEST"; + sc.Subjects = (const char *[1]){"foo"}; + sc.SubjectsLen = 1; + s = js_AddStream(NULL, js, &sc, NULL, &jerr); + testCond((s == NATS_OK) && (jerr == 0)); + + test("Publish test messages: "); + for (int i=0; (s == NATS_OK) && (i= 1); + + natsSubscription_Destroy(sub); + jsCtx_Destroy(jsSub); + natsConnection_Destroy(ncSub); + natsOptions_Destroy(subOpts); + + JS_TEARDOWN + _destroyDefaultThreadArgs(&args); +} + + + void test_JetStreamSubscribePullAsync(void) { natsStatus s;