@@ -928,6 +928,7 @@ int PartitionRaft::propose(
928928 const bsl::shared_ptr<mqbs::FileStore::PendingWrite>& pw)
929929{
930930 // executed by the partition *DISPATCHER* thread
931+ enum { rc_UNAVAILABLE = -1 };
931932
932933 // Compute the rollover footprint (DATA and QLIST bytes) from the write.
933934 // The JOURNAL reserve is always checked by 'rolloverIfNeeded'; only
@@ -946,17 +947,45 @@ int PartitionRaft::propose(
946947 *pw->d_appIdKeyPairs_p );
947948 }
948949
949- int rc = rolloverIfNeeded (dataBytes, qlistBytes);
950- if (0 != rc) {
951- return rc; // RETURN
950+ if (!d_fileStore_sp->isFileSetAvailable ()) {
951+ // The journal is read-only: a rollover could not reclaim enough space
952+ // (outstanding records exceed the policy threshold). Mirror legacy
953+ // 'FileStore::writeQueueOpRecord': the ONLY write still permitted is a
954+ // full-queue PURGE, written into the reserved PURGE area, which frees
955+ // outstanding records and lets 'onPurgeComplete' roll over to recover.
956+ // A per-appId purge (non-null appKey) writes per-message deletion
957+ // records, for which there is no room -- reject it, like any other
958+ // write, so the partition stays read-only.
959+ const bool isPurge = pw->d_recordType ==
960+ mqbs::RecordType::e_QUEUE_OP &&
961+ pw->d_queueOpType == mqbs::QueueOpType::e_PURGE &&
962+ pw->d_appKey .isNull ();
963+ if (!isPurge || !d_fileStore_sp->primaryHasPurgeReserve ()) {
964+ return rc_UNAVAILABLE; // RETURN
965+ }
966+
967+ BALL_LOG_WARN << " Partition [" << d_partitionId
968+ << " ] Writing PURGE record for queueKey ["
969+ << pw->d_queueKey
970+ << " ] into the reserved journal area despite the "
971+ << " partition being read-only (unavailable)." ;
972+ // Fall through: append the PURGE directly (no rollover); the reserved
973+ // area guarantees room.
952974 }
975+ else {
976+ int rc = rolloverIfNeeded (dataBytes, qlistBytes);
977+ if (0 != rc) {
978+ return rc; // RETURN
979+ }
953980
954- // If a rollover is in flight, buffer the write for replay into the new
955- // file once the rollover commits ('bufferPendingWrite' reserves
956- // 'pw->d_handle').
957- if (d_isRolloverPending) {
958- return d_raftLog_mp->bufferPendingWrite (pw,
959- d_raftNode_mp->currentTerm ());
981+ // If a rollover is in flight, buffer the write for replay into the new
982+ // file once the rollover commits ('bufferPendingWrite' reserves
983+ // 'pw->d_handle').
984+ if (d_isRolloverPending) {
985+ return d_raftLog_mp->bufferPendingWrite (
986+ pw,
987+ d_raftNode_mp->currentTerm ());
988+ }
960989 }
961990
962991 // Otherwise enqueue it for 'append()'; the record's sequence number
@@ -967,9 +996,9 @@ int PartitionRaft::propose(
967996 d_raftLog_mp->setPendingWrite (pw);
968997
969998 RaftNodeOutput output (d_allocator_p);
970- rc = d_raftNode_mp->propose (&output,
971- bsl::shared_ptr<bdlbb::Blob>(),
972- pw->d_id );
999+ int rc = d_raftNode_mp->propose (&output,
1000+ bsl::shared_ptr<bdlbb::Blob>(),
1001+ pw->d_id );
9731002 if (rc != 0 ) {
9741003 return rc;
9751004 }
@@ -1121,23 +1150,37 @@ int PartitionRaft::rolloverIfNeeded(bsls::Types::Uint64 dataBytes,
11211150 bsls::Types::Uint64 qlistBytes)
11221151{
11231152 // executed by the partition *DISPATCHER* thread
1124- enum { rc_SUCCESS = 0 };
1153+ enum { rc_SUCCESS = 0 , rc_READONLY = - 1 };
11251154
1126- if (!isLeader () ||
1127- !d_fileStore_sp->primaryNeedsRollover (dataBytes, qlistBytes)) {
1155+ if (!isLeader ()) {
11281156 return rc_SUCCESS; // RETURN
11291157 }
11301158
1131- // A rollover is required. Rather than NACK the triggering write, propose
1132- // 'e_ROLLOVER' (unless one is already in flight -- at most one uncommitted
1133- // rollover at a time) and return success. 'setPendingWrite()' then
1134- // buffers this triggering write, and every subsequent one, until the
1135- // rollover commits.
1136- if (!d_isRolloverPending) {
1137- proposeRollover ();
1159+ switch (d_fileStore_sp->primaryRolloverNeed (dataBytes, qlistBytes)) {
1160+ case mqbs::FileStore::e_ROLLOVER_NONE: {
1161+ return rc_SUCCESS; // RETURN
1162+ }
1163+ case mqbs::FileStore::e_ROLLOVER_READONLY: {
1164+ // A rollover cannot reclaim enough space -- the partition is full.
1165+ // 'primaryRolloverNeed' has already marked it read-only and panicked;
1166+ // NACK the triggering write rather than roll over into a same-size
1167+ // file and overflow it. The partition recovers only via a full purge
1168+ // (see 'onPurgeComplete').
1169+ return rc_READONLY; // RETURN
1170+ }
1171+ case mqbs::FileStore::e_ROLLOVER_NEEDED:
1172+ default : {
1173+ // A rollover is required and will reclaim enough space. Rather than
1174+ // NACK the triggering write, propose 'e_ROLLOVER' (unless one is
1175+ // already in flight -- at most one uncommitted rollover at a time).
1176+ // 'setPendingWrite()' then buffers this triggering write, and every
1177+ // subsequent one, until the rollover commits.
1178+ if (!d_isRolloverPending) {
1179+ proposeRollover ();
1180+ }
1181+ return rc_SUCCESS; // RETURN
1182+ }
11381183 }
1139-
1140- return rc_SUCCESS;
11411184}
11421185
11431186void PartitionRaft::drainPendingWrites ()
@@ -1658,9 +1701,18 @@ void PartitionRaft::onPurgeComplete()
16581701 // would drive 'rolloverImpl' directly -- outside the Raft log. Only the
16591702 // leader proposes, and only if the file set is at capacity;
16601703 // 'rolloverIfNeeded' is a no-op otherwise, so this is safe on replicas too
1661- // (they roll over via the committed 'e_ROLLOVER' apply hook). The legacy
1662- // 'd_journalFileAvailable' re-enable is not needed here: its false-setters
1663- // (legacy 'rolloverIfNeeded' / 'setActivePrimary') are legacy-only.
1704+ // (they roll over via the committed 'e_ROLLOVER' apply hook).
1705+ //
1706+ // If a full purge just recovered a read-only partition, the journal is
1707+ // marked unavailable. Re-enable it (mirrors legacy
1708+ // 'FileStore::onPurgeComplete') so the reclaiming rollover's sync-point
1709+ // marker can be written -- 'writeSyncPointRecord' refuses on an
1710+ // unavailable journal. 'rolloverIfNeeded' re-disables it if the freed
1711+ // space is still insufficient (a rollover would still overflow).
1712+ if (isLeader () && !d_fileStore_sp->isFileSetAvailable ()) {
1713+ d_fileStore_sp->setAvailabilityStatus (true );
1714+ }
1715+
16641716 rolloverIfNeeded (0 , 0 );
16651717}
16661718
0 commit comments