@@ -159,13 +159,17 @@ fromQuorumCheckerStatusJson(Json::Value const& value)
159159 }
160160
161161 auto statusInt = value.asUInt ();
162- if (statusInt > static_cast <unsigned >(QuorumCheckerStatus::UNKNOWN ))
163- {
162+ switch (statusInt)
163+ {
164+ case static_cast <unsigned >(QuorumCheckerStatus::UNSAT ):
165+ case static_cast <unsigned >(QuorumCheckerStatus::SAT ):
166+ case static_cast <unsigned >(QuorumCheckerStatus::UNKNOWN ):
167+ case static_cast <unsigned >(QuorumCheckerStatus::NO_QUORUM ):
168+ return static_cast <QuorumCheckerStatus>(statusInt);
169+ default :
164170 throw RustQuorumCheckerError (" Invalid status value: " +
165171 std::to_string (statusInt));
166172 }
167-
168- return static_cast <QuorumCheckerStatus>(statusInt);
169173}
170174
171175void
@@ -252,6 +256,7 @@ QuorumCheckerMetrics::QuorumCheckerMetrics()
252256 , mAbortedRun (0 )
253257 , mResultPotentialSplit (0 )
254258 , mResultUnknown (0 )
259+ , mResultNoQuorum (0 )
255260 , mCumulativeTimeMs (0 )
256261 , mCumulativeMemByte (0 )
257262{
@@ -293,6 +298,12 @@ QuorumCheckerMetrics::QuorumCheckerMetrics(Json::Value const& value)
293298 throw RustQuorumCheckerError (
294299 " Metrics missing or invalid 'result_unknown_count' field" );
295300 }
301+ if (!value.isMember (" result_no_quorum_count" ) ||
302+ !value[" result_no_quorum_count" ].isUInt ())
303+ {
304+ throw RustQuorumCheckerError (
305+ " Metrics missing or invalid 'result_no_quorum_count' field" );
306+ }
296307 if (!value.isMember (" cumulative_time_ms" ) ||
297308 !value[" cumulative_time_ms" ].isUInt64 ())
298309 {
@@ -310,6 +321,7 @@ QuorumCheckerMetrics::QuorumCheckerMetrics(Json::Value const& value)
310321 mAbortedRun = value[" aborted_run_count" ].asUInt64 ();
311322 mResultPotentialSplit = value[" result_potential_split_count" ].asUInt64 ();
312323 mResultUnknown = value[" result_unknown_count" ].asUInt64 ();
324+ mResultNoQuorum = value[" result_no_quorum_count" ].asUInt64 ();
313325 mCumulativeTimeMs = value[" cumulative_time_ms" ].asUInt64 ();
314326 mCumulativeMemByte = value[" cumulative_mem_byte" ].asUInt64 ();
315327}
@@ -323,6 +335,7 @@ QuorumCheckerMetrics::toJson()
323335 ret[" aborted_run_count" ] = Json::UInt64 (mAbortedRun );
324336 ret[" result_potential_split_count" ] = Json::UInt64 (mResultPotentialSplit );
325337 ret[" result_unknown_count" ] = Json::UInt64 (mResultUnknown );
338+ ret[" result_no_quorum_count" ] = Json::UInt64 (mResultNoQuorum );
326339 ret[" cumulative_time_ms" ] = Json::UInt64 (mCumulativeTimeMs );
327340 ret[" cumulative_mem_byte" ] = Json::UInt64 (mCumulativeMemByte );
328341 return ret;
@@ -337,6 +350,7 @@ QuorumCheckerMetrics::flush(MetricsRegistry& metrics)
337350 metrics.NewCounter ({" scp" , " qic" , " result-potential-split" })
338351 .inc (mResultPotentialSplit );
339352 metrics.NewCounter ({" scp" , " qic" , " result-unknown" }).inc (mResultUnknown );
353+ metrics.NewCounter ({" scp" , " qic" , " result-no-quorum" }).inc (mResultNoQuorum );
340354 metrics.NewMeter ({" scp" , " qic" , " cumulative-time-ms" }, " milli-second" )
341355 .Mark (mCumulativeTimeMs );
342356 metrics.NewMeter ({" scp" , " qic" , " cumulative-mem-byte" }, " byte" )
@@ -346,6 +360,7 @@ QuorumCheckerMetrics::flush(MetricsRegistry& metrics)
346360 mAbortedRun = 0 ;
347361 mResultPotentialSplit = 0 ;
348362 mResultUnknown = 0 ;
363+ mResultNoQuorum = 0 ;
349364 mCumulativeTimeMs = 0 ;
350365 mCumulativeMemByte = 0 ;
351366}
@@ -396,6 +411,10 @@ checkQuorumIntersectionInner(
396411 {
397412 metrics.mResultPotentialSplit += 1 ;
398413 }
414+ else if (status == QuorumCheckerStatus::NO_QUORUM )
415+ {
416+ metrics.mResultNoQuorum += 1 ;
417+ }
399418
400419 // Update time limit. Memory is a transient resource that gets reclaimed
401420 // back.
@@ -565,10 +584,12 @@ runQuorumIntersectionCheckAsync(
565584
566585 // Note: the ecode should match the return code from the command
567586 // line-running process which is just `QuorumCheckerStatus` as integer
568- // on success. However, if the command fails due to abort (if exceeding
569- // the memory limit), the ecode=1 will be returned because of the
570- // simplification of collapsing all non-WIFEXITED exits to error code 1
571- // (see `mapExitStatusToErrorCode` in ProcessManagerImpl.cpp).
587+ // on success. Exceeding the time or (estimated) memory limit is now a
588+ // recoverable solver error that surfaces as `UNKNOWN` (102), not a
589+ // process abort. If the command dies abnormally (crash/signal, i.e. a
590+ // non-WIFEXITED exit), ecode=1 is returned because of the
591+ // simplification of collapsing all such exits to error code 1 (see
592+ // `mapExitStatusToErrorCode` in ProcessManagerImpl.cpp).
572593 int ecode = ec.value ();
573594 CLOG_DEBUG (SCP ,
574595 " Processing quorum intersection check result: numNodes={}, "
@@ -579,17 +600,23 @@ runQuorumIntersectionCheckAsync(
579600
580601 if (ecode == static_cast <int >(QuorumCheckerStatus::UNSAT ) ||
581602 ecode == static_cast <int >(QuorumCheckerStatus::SAT ) ||
582- ecode == static_cast <int >(QuorumCheckerStatus::UNKNOWN ))
603+ ecode == static_cast <int >(QuorumCheckerStatus::UNKNOWN ) ||
604+ ecode == static_cast <int >(QuorumCheckerStatus::NO_QUORUM ))
583605 {
584606 try
585607 {
586608 auto res = parseResultsJson (qicResultJson);
587609 hStateSP->mStatus = fromQuorumCheckerStatusJson (res[" status" ]);
588610 QuorumCheckerMetrics metrics (res[" metrics" ]);
589611 metrics.flush (hStateSP->mMetrics );
590- // only update the following info if we had a complete run
612+ // only update the following info if we had a complete run.
613+ // NO_QUORUM is a complete result (the checker definitively
614+ // determined the FBAS has no quorum) so we record it too, but
615+ // unlike UNSAT it is not a "good" result and must not advance
616+ // mLastGoodLedger.
591617 if (ecode == static_cast <int >(QuorumCheckerStatus::UNSAT ) ||
592- ecode == static_cast <int >(QuorumCheckerStatus::SAT ))
618+ ecode == static_cast <int >(QuorumCheckerStatus::SAT ) ||
619+ ecode == static_cast <int >(QuorumCheckerStatus::NO_QUORUM ))
593620 {
594621 hStateSP->mNumNodes = numNodes;
595622 hStateSP->mLastCheckLedger = ledger;
0 commit comments