Skip to content

Commit 8d5a14c

Browse files
committed
kernel-proxy: address PR #514 review findings
1 parent f8e8f76 commit 8d5a14c

4 files changed

Lines changed: 71 additions & 16 deletions

File tree

flagcx/core/flagcx_hetero.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,8 @@ flagcxResult_t flagcxHeteroFlushRma(flagcxHeteroComm_t comm, int peer,
789789
return flagcxRemoteError;
790790
usleep(100);
791791
}
792-
// Check rmaError after wait: doneSeqs may have been advanced despite failure
792+
// Check rmaError after wait: kernel proxy direct-post path advances doneSeqs
793+
// unconditionally (even on failure) to prevent flush hangs.
793794
if (__atomic_load_n(&proxy->rmaError, __ATOMIC_ACQUIRE))
794795
return flagcxRemoteError;
795796
return flagcxSuccess;
@@ -830,6 +831,10 @@ flagcxResult_t flagcxHeteroFlushAllRma(flagcxHeteroComm_t comm) {
830831
usleep(100);
831832
}
832833
}
834+
// Check rmaError after wait: kernel proxy direct-post path advances doneSeqs
835+
// unconditionally (even on failure) to prevent flush hangs.
836+
if (__atomic_load_n(&proxy->rmaError, __ATOMIC_ACQUIRE))
837+
return flagcxRemoteError;
833838
return flagcxSuccess;
834839
}
835840

flagcx/core/proxy.cc

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <string>
2525
#include <sys/syscall.h>
2626
#include <sys/time.h>
27+
#include <time.h>
2728
#include <unistd.h>
2829
using namespace std;
2930

@@ -1402,6 +1403,8 @@ void *flagcxProxyService(void *args) {
14021403
// Bypasses RMA Proxy: posts IB ops directly from kernel proxy thread.
14031404
// ============================================================================
14041405

1406+
// Note: each kernel proxy thread allocates nRanks × (256 × 16B + 8B) ≈
1407+
// 4KB/peer. With contextCount threads, total is contextCount × nRanks × 4KB.
14051408
#define FLAGCX_KPROXY_MAX_INFLIGHT 256
14061409
#define FLAGCX_KPROXY_RING_MASK (FLAGCX_KPROXY_MAX_INFLIGHT - 1)
14071410

@@ -1426,7 +1429,9 @@ static void flagcxKernelProxyPoll(struct flagcxKernelProxyState *state,
14261429
struct flagcxHeteroComm *comm) {
14271430
struct flagcxRmaProxyState *proxy = comm->rmaProxy;
14281431
struct flagcxNetAdaptor *net = comm->netAdaptor;
1429-
bool anyCompleted = false;
1432+
if (proxy == NULL || net == NULL)
1433+
return;
1434+
bool anyRetired = false;
14301435
for (int p = 0; p < state->nRanks; p++) {
14311436
struct flagcxKernelProxyPeerState *ps = &state->peers[p];
14321437
while (ps->head != ps->tail) {
@@ -1451,6 +1456,7 @@ static void flagcxKernelProxyPoll(struct flagcxKernelProxyState *state,
14511456
if (!done)
14521457
break;
14531458
ps->head++;
1459+
anyRetired = true;
14541460
// Always advance doneSeqs (even on failure) to prevent flush hangs.
14551461
__atomic_store_n((uint64_t *)&proxy->doneSeqs[p], inf->opSeq,
14561462
__ATOMIC_RELEASE);
@@ -1459,13 +1465,11 @@ static void flagcxKernelProxyPoll(struct flagcxKernelProxyState *state,
14591465
__ATOMIC_RELEASE);
14601466
if (!failed) {
14611467
__atomic_fetch_add(&proxy->completionCount, 1ULL, __ATOMIC_RELEASE);
1462-
anyCompleted = true;
14631468
}
14641469
}
14651470
}
1466-
// Batch the broadcast: signal waiters once per poll sweep, not per
1467-
// completion.
1468-
if (anyCompleted && !proxy->useStreamOps) {
1471+
// Batch the broadcast: signal waiters once per poll sweep on any retirement.
1472+
if (anyRetired && !proxy->useStreamOps) {
14691473
pthread_mutex_lock(&proxy->doneMutex);
14701474
pthread_cond_broadcast(&proxy->doneCond);
14711475
pthread_mutex_unlock(&proxy->doneMutex);
@@ -1504,12 +1508,18 @@ flagcxKernelProxyPost(struct flagcxKernelProxyState *state,
15041508
}
15051509

15061510
// Back-pressure: if ring is full, poll until a slot frees.
1507-
int spins = 0;
1511+
struct timespec deadline;
1512+
clock_gettime(CLOCK_MONOTONIC, &deadline);
1513+
deadline.tv_sec += 30; // 30s timeout
15081514
while ((ps->tail - ps->head) >= FLAGCX_KPROXY_MAX_INFLIGHT) {
15091515
flagcxKernelProxyPoll(state, comm);
15101516
if ((ps->tail - ps->head) >= FLAGCX_KPROXY_MAX_INFLIGHT) {
1511-
if (++spins > 10000000) {
1512-
WARN("flagcxKernelProxyPost: back-pressure timeout peer=%d", peer);
1517+
struct timespec now;
1518+
clock_gettime(CLOCK_MONOTONIC, &now);
1519+
if (now.tv_sec > deadline.tv_sec ||
1520+
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) {
1521+
WARN("flagcxKernelProxyPost: back-pressure timeout (30s) peer=%d",
1522+
peer);
15131523
__atomic_store_n((int *)&proxy->rmaError, 1, __ATOMIC_RELEASE);
15141524
return flagcxInternalError;
15151525
}
@@ -1518,13 +1528,22 @@ flagcxKernelProxyPost(struct flagcxKernelProxyState *state,
15181528
}
15191529

15201530
void *sendComm = comm->oneSideHandles[0]->fullSendComms[peer];
1531+
if (sendComm == NULL) {
1532+
WARN("flagcxKernelProxyPost: sendComm is NULL for peer=%d", peer);
1533+
return flagcxInternalError;
1534+
}
15211535
void **srcHandles = NULL, **dstHandles = NULL;
1522-
if (size > 0 && srcMrIdx >= 0) {
1536+
if (size > 0 && srcMrIdx >= 0 && dstMrIdx >= 0) {
15231537
srcHandles = (void **)comm->oneSideHandles[srcMrIdx];
15241538
dstHandles = (void **)comm->oneSideHandles[dstMrIdx];
15251539
}
15261540

1527-
// Assign opSeq (shared atomic counter with RMA Proxy for ordering)
1541+
// Assign opSeq (shared atomic counter with RMA Proxy for ordering).
1542+
// Single-writer invariant: for a given peer, only the kernel proxy thread
1543+
// (this path) or the RMA Proxy thread (stream path) posts ops at any time.
1544+
// This ensures opSeqs are assigned and complete in order, so doneSeqs
1545+
// advances monotonically. Violating this invariant would require per-peer
1546+
// max-tracking instead of direct store.
15281547
uint64_t opSeq =
15291548
__atomic_add_fetch((uint64_t *)&proxy->opSeqs[peer], 1, __ATOMIC_RELAXED);
15301549

@@ -1550,6 +1569,28 @@ flagcxKernelProxyPost(struct flagcxKernelProxyState *state,
15501569
break;
15511570
}
15521571
case FLAGCX_RMA_PUT_VALUE: {
1572+
// Serialize PUT_VALUE per-peer: drain all prior ops to ensure the staging
1573+
// slot is idle. The NIC reads staging asynchronously, so concurrent
1574+
// PUT_VALUE to the same peer would corrupt the buffer.
1575+
struct timespec pvDeadline;
1576+
clock_gettime(CLOCK_MONOTONIC, &pvDeadline);
1577+
pvDeadline.tv_sec += 30;
1578+
while (ps->head != ps->tail) {
1579+
flagcxKernelProxyPoll(state, comm);
1580+
if (ps->head != ps->tail) {
1581+
struct timespec now;
1582+
clock_gettime(CLOCK_MONOTONIC, &now);
1583+
if (now.tv_sec > pvDeadline.tv_sec ||
1584+
(now.tv_sec == pvDeadline.tv_sec &&
1585+
now.tv_nsec >= pvDeadline.tv_nsec)) {
1586+
WARN("flagcxKernelProxyPost: PUT_VALUE drain timeout (30s) peer=%d",
1587+
peer);
1588+
__atomic_store_n((int *)&proxy->rmaError, 1, __ATOMIC_RELEASE);
1589+
return flagcxInternalError;
1590+
}
1591+
sched_yield();
1592+
}
1593+
}
15531594
struct flagcxOneSideHandleInfo *stagingH = comm->stagingHandle;
15541595
if (stagingH == NULL || stagingH->baseVas == NULL) {
15551596
WARN("flagcxKernelProxyPost: staging handles not initialized");
@@ -1593,6 +1634,8 @@ static void flagcxKernelProxyDrain(struct flagcxKernelProxyState *state,
15931634
return;
15941635
struct flagcxNetAdaptor *net = comm->netAdaptor;
15951636
struct flagcxRmaProxyState *proxy = comm->rmaProxy;
1637+
if (net == NULL || proxy == NULL)
1638+
return;
15961639
for (int p = 0; p < state->nRanks; p++) {
15971640
struct flagcxKernelProxyPeerState *ps = &state->peers[p];
15981641
while (ps->head != ps->tail) {
@@ -1609,6 +1652,8 @@ static void flagcxKernelProxyDrain(struct flagcxKernelProxyState *state,
16091652
done = 1;
16101653
break;
16111654
}
1655+
if (!done)
1656+
sched_yield();
16121657
}
16131658
succeeded = !testFailed;
16141659
}

test/unittest/rma/coll_rma_get.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ TEST_F(RmaTest, GetSmall) {
7474
for (size_t i = 0; i < testSize; ++i) {
7575
if (received[i] != 0xCD) {
7676
mismatches++;
77-
if (mismatches == 1)
77+
if (mismatches == 1) {
7878
EXPECT_EQ(received[i], 0xCD) << "Mismatch at byte " << i;
79+
}
7980
}
8081
}
8182
EXPECT_EQ(mismatches, 0);
@@ -130,8 +131,9 @@ TEST_F(RmaTest, GetLarge) {
130131
uint8_t expected = static_cast<uint8_t>((i * 7) & 0xFF);
131132
if (received[i] != expected) {
132133
mismatches++;
133-
if (mismatches == 1)
134+
if (mismatches == 1) {
134135
EXPECT_EQ(received[i], expected) << "Mismatch at byte " << i;
136+
}
135137
}
136138
}
137139
EXPECT_EQ(mismatches, 0);
@@ -184,9 +186,10 @@ TEST_F(RmaTest, GetBidirectional) {
184186
uint8_t expected = static_cast<uint8_t>((peer + 1 + i) & 0xFF);
185187
if (received[i] != expected) {
186188
mismatches++;
187-
if (mismatches == 1)
189+
if (mismatches == 1) {
188190
EXPECT_EQ(received[i], expected)
189191
<< "Mismatch at byte " << i << " reading from rank " << peer;
192+
}
190193
}
191194
}
192195
EXPECT_EQ(mismatches, 0);

test/unittest/rma/coll_rma_put.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ TEST_F(RmaTest, PutSignalSmall) {
7777
for (size_t i = 0; i < testSize; ++i) {
7878
if (received[i] != 0xAB) {
7979
mismatches++;
80-
if (mismatches == 1)
80+
if (mismatches == 1) {
8181
EXPECT_EQ(received[i], 0xAB) << "Mismatch at byte " << i;
82+
}
8283
}
8384
}
8485
EXPECT_EQ(mismatches, 0);
@@ -132,8 +133,9 @@ TEST_F(RmaTest, PutSignalLarge) {
132133
uint8_t expected = static_cast<uint8_t>(i & 0xFF);
133134
if (received[i] != expected) {
134135
mismatches++;
135-
if (mismatches == 1)
136+
if (mismatches == 1) {
136137
EXPECT_EQ(received[i], expected) << "Mismatch at byte " << i;
138+
}
137139
}
138140
}
139141
EXPECT_EQ(mismatches, 0);

0 commit comments

Comments
 (0)