Skip to content

Commit e0eb42b

Browse files
committed
Fix from review
1 parent 65128c1 commit e0eb42b

3 files changed

Lines changed: 95 additions & 10 deletions

File tree

src/mqtt_client.c

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,24 @@ static int MqttClient_HandlePacket(MqttClient* client,
12241224
if (packet_type == MQTT_PACKET_TYPE_PUBLISH_REC &&
12251225
client->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5 &&
12261226
(((MqttPublishResp*)packet_obj)->reason_code & 0x80)) {
1227+
#ifdef WOLFMQTT_MULTITHREAD
1228+
/* The QoS 2 exchange ends here with no PUBCOMP [MQTT-4.9], so
1229+
* the reserved Receive Maximum unit for this packet id would be
1230+
* leaked. Release it via the PUBCOMP pending response (the only
1231+
* way a write-only publisher, whose thread never returns, can be
1232+
* credited). Idempotent, so an ordinary waiting publisher is
1233+
* unaffected. */
1234+
MqttPendResp* qpr = NULL;
1235+
if (wm_SemLock(&client->lockClient) == 0) {
1236+
if (MqttClient_RespList_Find(client,
1237+
MQTT_PACKET_TYPE_PUBLISH_COMP, packet_id, &qpr) &&
1238+
qpr != NULL && qpr->recvQuotaStat != NULL) {
1239+
MqttClient_RecvQuotaRelease_Locked(client,
1240+
qpr->recvQuotaStat);
1241+
}
1242+
wm_SemUnlock(&client->lockClient);
1243+
}
1244+
#endif
12271245
return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PUBLISH_REJECTED);
12281246
}
12291247
#endif
@@ -1703,10 +1721,10 @@ static int MqttClient_WaitType(MqttClient *client, void *packet_obj,
17031721
* but does not wait for its own ack; release it here as this
17041722
* reading thread completes the terminal PUBACK / PUBCOMP,
17051723
* via the reserving stat recorded on the pending response.
1706-
* Idempotent, so an ordinary publish that also releases in
1707-
* its waiting thread is unaffected. */
1708-
if (rc == MQTT_CODE_SUCCESS &&
1709-
pendResp->recvQuotaStat != NULL) {
1724+
* Only reached on a completed (non-error) response, so no
1725+
* result gate is needed. Idempotent, so an ordinary publish
1726+
* that also releases in its waiting thread is unaffected. */
1727+
if (pendResp->recvQuotaStat != NULL) {
17101728
MqttClient_RecvQuotaRelease_Locked(client,
17111729
pendResp->recvQuotaStat);
17121730
}
@@ -2950,7 +2968,13 @@ static int MqttPublishMsg(MqttClient *client, MqttPublish *publish,
29502968
break;
29512969
#endif
29522970
#ifdef WOLFMQTT_MULTITHREAD
2953-
if (wm_SemLock(&client->lockClient) == 0) {
2971+
/* Leave a write-only publish's pending response in the list: a
2972+
* build without WOLFMQTT_NONBLOCK reports success here without
2973+
* the ack, and unlinking it now would prevent the reading thread
2974+
* from completing it and releasing the reserved Receive Maximum
2975+
* unit, leaking quota. The reader removes it when the ack
2976+
* arrives. */
2977+
if (!writeOnly && wm_SemLock(&client->lockClient) == 0) {
29542978
MqttClient_RespList_Remove(client, &publish->pendResp);
29552979
wm_SemUnlock(&client->lockClient);
29562980
}

tests/test_mqtt_client.c

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2234,7 +2234,9 @@ TEST(publish_qos2_v5_pubrec_rejection_multithread_reader)
22342234
/* The publisher's own struct is NOT updated on this path. */
22352235
ASSERT_EQ(MQTT_REASON_SUCCESS, publish.resp.reason_code);
22362236
}
2237+
#endif /* WOLFMQTT_MULTITHREAD && WOLFMQTT_NONBLOCK */
22372238

2239+
#ifdef WOLFMQTT_MULTITHREAD
22382240
/* [MQTT-3.3.4-9] Receive Maximum bounds unacknowledged QoS>0 PUBLISH packets,
22392241
* and MqttClient_Publish_WriteOnly must honor it like any other publish. With
22402242
* the quota exhausted, a write-only QoS 1 publish must be refused before
@@ -2298,9 +2300,13 @@ TEST(publish_writeonly_v5_receive_max_released_on_puback)
22982300
publish.total_len = (word32)(sizeof(payload) - 1);
22992301
publish.buffer_len = publish.total_len;
23002302

2301-
rc = MqttClient_Publish_WriteOnly(&test_client, &publish, NULL);
2302-
/* The single unit was reserved and the call returned without waiting. */
2303-
ASSERT_EQ(MQTT_CODE_CONTINUE, rc);
2303+
/* Drive the write-only publish to completion. It returns CONTINUE under
2304+
* WOLFMQTT_NONBLOCK and SUCCESS in a blocking MT build; either way the unit
2305+
* is reserved and outstanding until the reader processes the ack. */
2306+
rc = MQTT_CODE_CONTINUE;
2307+
for (i = 0; i < 20 && rc == MQTT_CODE_CONTINUE; i++) {
2308+
rc = MqttClient_Publish_WriteOnly(&test_client, &publish, NULL);
2309+
}
23042310
ASSERT_EQ(0, test_client.server_recv_max);
23052311

23062312
/* Reading thread processes the PUBACK. */
@@ -2315,7 +2321,55 @@ TEST(publish_writeonly_v5_receive_max_released_on_puback)
23152321
/* The reserved unit was credited back rather than leaked. */
23162322
ASSERT_EQ(1, test_client.server_recv_max);
23172323
}
2318-
#endif /* WOLFMQTT_MULTITHREAD && WOLFMQTT_NONBLOCK */
2324+
2325+
/* A write-only QoS 2 publish reserves a unit; a broker rejection at the PUBREC
2326+
* stage (reason >= 0x80) ends the exchange with no PUBCOMP, so the unit must
2327+
* still be credited back rather than leaked. */
2328+
TEST(publish_writeonly_v5_receive_max_released_on_pubrec_reject)
2329+
{
2330+
int rc;
2331+
int i;
2332+
static MqttPublish publish;
2333+
static byte payload[] = "hello";
2334+
/* v5 PUBREC: type=0x50, remain=3, packet_id=22 (0x16), reason=0x97. */
2335+
static const byte pubrec[] = { 0x50, 0x03, 0x00, 0x16, 0x97 };
2336+
2337+
rc = test_init_client();
2338+
ASSERT_EQ(MQTT_CODE_SUCCESS, rc);
2339+
test_client.protocol_level = MQTT_CONNECT_PROTOCOL_LEVEL_5;
2340+
test_client.server_recv_max = 1;
2341+
test_client.server_recv_max_negotiated = 1;
2342+
2343+
test_net.write = mock_net_write_accept;
2344+
test_net.read = mock_net_read_canned;
2345+
2346+
XMEMSET(&publish, 0, sizeof(publish));
2347+
publish.qos = MQTT_QOS_2;
2348+
publish.packet_id = 22;
2349+
publish.topic_name = "test/topic";
2350+
publish.buffer = payload;
2351+
publish.total_len = (word32)(sizeof(payload) - 1);
2352+
publish.buffer_len = publish.total_len;
2353+
2354+
rc = MQTT_CODE_CONTINUE;
2355+
for (i = 0; i < 20 && rc == MQTT_CODE_CONTINUE; i++) {
2356+
rc = MqttClient_Publish_WriteOnly(&test_client, &publish, NULL);
2357+
}
2358+
ASSERT_EQ(0, test_client.server_recv_max);
2359+
2360+
/* Reading thread processes the rejecting PUBREC. */
2361+
XMEMCPY(g_canned_buf, pubrec, sizeof(pubrec));
2362+
g_canned_len = (int)sizeof(pubrec);
2363+
g_canned_pos = 0;
2364+
rc = MQTT_CODE_CONTINUE;
2365+
for (i = 0; i < 20 && rc == MQTT_CODE_CONTINUE; i++) {
2366+
rc = MqttClient_WaitMessage(&test_client, TEST_CMD_TIMEOUT_MS);
2367+
}
2368+
2369+
/* The reserved unit was credited back despite the rejection. */
2370+
ASSERT_EQ(1, test_client.server_recv_max);
2371+
}
2372+
#endif /* WOLFMQTT_MULTITHREAD */
23192373
#endif /* WOLFMQTT_V5 */
23202374

23212375
/* Regression test for MQTT Packet Identifier in-use collision check. The
@@ -3597,8 +3651,11 @@ void run_mqtt_client_tests(void)
35973651
RUN_TEST(publish_qos1_v5_write_failure_restores_recv_quota);
35983652
#if defined(WOLFMQTT_MULTITHREAD) && defined(WOLFMQTT_NONBLOCK)
35993653
RUN_TEST(publish_qos2_v5_pubrec_rejection_multithread_reader);
3654+
#endif
3655+
#ifdef WOLFMQTT_MULTITHREAD
36003656
RUN_TEST(publish_writeonly_v5_receive_max_quota_exhausted_rejects);
36013657
RUN_TEST(publish_writeonly_v5_receive_max_released_on_puback);
3658+
RUN_TEST(publish_writeonly_v5_receive_max_released_on_pubrec_reject);
36023659
#endif
36033660
#endif
36043661
#if defined(WOLFMQTT_MULTITHREAD) && defined(WOLFMQTT_NONBLOCK)

wolfmqtt/mqtt_packet.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,11 @@ typedef struct _MqttMsgStat {
336336

337337
byte isReadActive:1;
338338
byte isWriteActive:1;
339-
byte recvQuotaHeld:1; /* v5 Receive Maximum unit reserved for this message */
339+
/* v5 Receive Maximum unit reserved for this message. Its own storage unit,
340+
* not a bitfield, because a reader thread may clear it (under lockClient)
341+
* while the owning thread writes isReadActive/isWriteActive outside that
342+
* lock; sharing a byte would make those a racy read-modify-write. */
343+
byte recvQuotaHeld;
340344
} MqttMsgStat;
341345

342346
#ifdef WOLFMQTT_MULTITHREAD

0 commit comments

Comments
 (0)