Make some more safety improvements to dispatch to the Matter queue. (#24084)

The idea is to minimize the number of places that have to have
isRunning checks, and direct access to the Matter queue, by having
APIs that do those for you.
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
index eb39a86..752d14c 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
@@ -137,7 +137,7 @@
 
     // Destroy read clients in the work queue
     [controller
-        asyncDispatchToMatterQueue:^(Controller::DeviceCommissioner * commissioner) {
+        asyncDispatchToMatterQueue:^() {
             for (MTRReadClientContainer * container in listToDelete) {
                 if (container.readClientPtr) {
                     Platform::Delete(container.readClientPtr);
@@ -193,7 +193,7 @@
     [readClientContainersLock unlock];
 
     [controller
-        asyncDispatchToMatterQueue:^(Controller::DeviceCommissioner * commissioner) {
+        asyncDispatchToMatterQueue:^() {
             for (MTRReadClientContainer * container in listToFail) {
                 // Send auto resubscribe request again by read clients, which must fail.
                 chip::app::ReadPrepareParams readParams;
@@ -1373,7 +1373,7 @@
     }
 
     [self.deviceController
-        asyncDispatchToMatterQueue:^(Controller::DeviceCommissioner * commissioner) {
+        asyncGetCommissionerOnMatterQueue:^(Controller::DeviceCommissioner * commissioner) {
             auto resultCallback = ^(CHIP_ERROR status, const SetupPayload & payload) {
                 if (status != CHIP_NO_ERROR) {
                     dispatch_async(queue, ^{
diff --git a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase_internal.h b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase_internal.h
index c6ef366..4304f15 100644
--- a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase_internal.h
+++ b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase_internal.h
@@ -139,7 +139,7 @@
         LogRequestStart();
 
         [device.deviceController
-            asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+            asyncDispatchToMatterQueue:^() {
                 CHIP_ERROR err = action(mSuccess, mFailure);
                 if (err != CHIP_NO_ERROR) {
                     NSLog(@"Failure performing action. C++-mangled success callback type: '%s', error: %s", typeid(T).name(),
@@ -159,31 +159,22 @@
     {
         LogRequestStart();
 
-        BOOL ok = [device.deviceController
-            getSessionForCommissioneeDevice:device.nodeID
-                                 completion:^(chip::Messaging::ExchangeManager * exchangeManager,
-                                     const chip::Optional<chip::SessionHandle> & session, NSError * error) {
-                                     MaybeDoAction(exchangeManager, session, error);
-                                 }];
-
-        if (ok == NO) {
-            OnFailureFn(this, CHIP_ERROR_INCORRECT_STATE);
-        }
+        [device.deviceController getSessionForCommissioneeDevice:device.nodeID
+                                                      completion:^(chip::Messaging::ExchangeManager * exchangeManager,
+                                                          const chip::Optional<chip::SessionHandle> & session, NSError * error) {
+                                                          MaybeDoAction(exchangeManager, session, error);
+                                                      }];
     }
 
     void ActionWithNodeID(chip::NodeId nodeID, MTRDeviceController * controller)
     {
         LogRequestStart();
 
-        BOOL ok = [controller getSessionForNode:nodeID
-                                     completion:^(chip::Messaging::ExchangeManager * exchangeManager,
-                                         const chip::Optional<chip::SessionHandle> & session, NSError * error) {
-                                         MaybeDoAction(exchangeManager, session, error);
-                                     }];
-
-        if (ok == NO) {
-            OnFailureFn(this, CHIP_ERROR_INCORRECT_STATE);
-        }
+        [controller getSessionForNode:nodeID
+                           completion:^(chip::Messaging::ExchangeManager * exchangeManager,
+                               const chip::Optional<chip::SessionHandle> & session, NSError * error) {
+                               MaybeDoAction(exchangeManager, session, error);
+                           }];
     }
 
     void LogRequestStart()
diff --git a/src/darwin/Framework/CHIP/MTRClusterStateCacheContainer.mm b/src/darwin/Framework/CHIP/MTRClusterStateCacheContainer.mm
index 7550e1e..1027d00 100644
--- a/src/darwin/Framework/CHIP/MTRClusterStateCacheContainer.mm
+++ b/src/darwin/Framework/CHIP/MTRClusterStateCacheContainer.mm
@@ -137,7 +137,7 @@
     }
 
     [self.baseDevice.deviceController
-        asyncDispatchToMatterQueue:^(Controller::DeviceCommissioner *) {
+        asyncDispatchToMatterQueue:^() {
             if (endpointID == nil && clusterID == nil) {
                 MTR_LOG_ERROR(
                     "Error: currently read from attribute cache does not support wildcards for both endpoint and cluster");
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index f4d6480..0fa42fe 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -123,7 +123,6 @@
         if ([self checkForInitError:(_operationalCredentialsDelegate != nullptr) logMsg:kErrorOperationalCredentialsInit]) {
             return nil;
         }
-        _operationalCredentialsDelegate->setChipWorkQueue(_chipWorkQueue);
     }
     return self;
 }
@@ -544,13 +543,11 @@
 
 - (void)setDeviceControllerDelegate:(id<MTRDeviceControllerDelegate>)delegate queue:(dispatch_queue_t)queue
 {
-    VerifyOrReturn([self checkIsRunning]);
-
-    dispatch_async(_chipWorkQueue, ^{
-        VerifyOrReturn([self checkIsRunning]);
-
-        self->_deviceControllerDelegateBridge->setDelegate(self, delegate, queue);
-    });
+    [self
+        asyncDispatchToMatterQueue:^() {
+            self->_deviceControllerDelegateBridge->setDelegate(self, delegate, queue);
+        }
+                      errorHandler:nil];
 }
 
 - (BOOL)setOperationalCertificateIssuer:(nullable id<MTROperationalCertificateIssuer>)operationalCertificateIssuer
@@ -692,68 +689,54 @@
     return deviceProxy->GetDeviceTransportType() == chip::Transport::Type::kBle;
 }
 
-- (BOOL)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion
+- (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion
 {
-    if (![self checkIsRunning]) {
-        return NO;
-    }
+    [self
+        asyncGetCommissionerOnMatterQueue:^(chip::Controller::DeviceCommissioner * commissioner) {
+            auto connectionBridge = new MTRDeviceConnectionBridge(completion);
 
-    dispatch_async(_chipWorkQueue, ^{
-        NSError * error;
-        if (![self checkIsRunning:&error]) {
-            completion(nullptr, chip::NullOptional, error);
-            return;
+            // MTRDeviceConnectionBridge always delivers errors async via
+            // completion.
+            connectionBridge->connect(commissioner, nodeID);
         }
-
-        auto connectionBridge = new MTRDeviceConnectionBridge(completion);
-
-        // MTRDeviceConnectionBridge always delivers errors async via
-        // completion.
-        connectionBridge->connect(self->_cppCommissioner, nodeID);
-    });
-
-    return YES;
+        errorHandler:^(NSError * error) {
+            completion(nullptr, chip::NullOptional, error);
+        }];
 }
 
-- (BOOL)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion
+- (void)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion
 {
-    if (![self checkIsRunning]) {
-        return NO;
-    }
+    [self
+        asyncGetCommissionerOnMatterQueue:^(chip::Controller::DeviceCommissioner * commissioner) {
+            chip::CommissioneeDeviceProxy * deviceProxy;
+            CHIP_ERROR err = commissioner->GetDeviceBeingCommissioned(deviceID, &deviceProxy);
+            if (err != CHIP_NO_ERROR) {
+                completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:err]);
+                return;
+            }
 
-    dispatch_async(_chipWorkQueue, ^{
-        NSError * error;
-        if (![self checkIsRunning:&error]) {
+            chip::Optional<chip::SessionHandle> session = deviceProxy->GetSecureSession();
+            if (!session.HasValue() || !session.Value()->AsSecureSession()->IsPASESession()) {
+                completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE]);
+                return;
+            }
+
+            completion(deviceProxy->GetExchangeManager(), session, nil);
+        }
+        errorHandler:^(NSError * error) {
             completion(nullptr, chip::NullOptional, error);
-            return;
-        }
-
-        chip::CommissioneeDeviceProxy * deviceProxy;
-        CHIP_ERROR err = self->_cppCommissioner->GetDeviceBeingCommissioned(deviceID, &deviceProxy);
-        if (err != CHIP_NO_ERROR) {
-            completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:err]);
-            return;
-        }
-
-        chip::Optional<chip::SessionHandle> session = deviceProxy->GetSecureSession();
-        if (!session.HasValue() || !session.Value()->AsSecureSession()->IsPASESession()) {
-            completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE]);
-            return;
-        }
-
-        completion(deviceProxy->GetExchangeManager(), session, nil);
-    });
-
-    return YES;
+        }];
 }
 
-- (void)asyncDispatchToMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
-                      errorHandler:(void (^)(NSError *))errorHandler
+- (void)asyncGetCommissionerOnMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
+                             errorHandler:(nullable MTRDeviceErrorHandler)errorHandler
 {
     {
         NSError * error;
         if (![self checkIsRunning:&error]) {
-            errorHandler(error);
+            if (errorHandler != nil) {
+                errorHandler(error);
+            }
             return;
         }
     }
@@ -761,7 +744,9 @@
     dispatch_async(_chipWorkQueue, ^{
         NSError * error;
         if (![self checkIsRunning:&error]) {
-            errorHandler(error);
+            if (errorHandler != nil) {
+                errorHandler(error);
+            }
             return;
         }
 
@@ -769,6 +754,14 @@
     });
 }
 
+- (void)asyncDispatchToMatterQueue:(dispatch_block_t)block errorHandler:(nullable MTRDeviceErrorHandler)errorHandler
+{
+    auto adapter = ^(chip::Controller::DeviceCommissioner *) {
+        block();
+    };
+    [self asyncGetCommissionerOnMatterQueue:adapter errorHandler:errorHandler];
+}
+
 - (void)syncRunOnWorkQueue:(SyncWorkQueueBlock)block error:(NSError * __autoreleasing *)error
 {
     VerifyOrReturn([self checkIsRunning:error]);
@@ -782,13 +775,11 @@
 - (id)syncRunOnWorkQueueWithReturnValue:(SyncWorkQueueBlockWithReturnValue)block error:(NSError * __autoreleasing *)error
 {
     __block id rv = nil;
-
-    VerifyOrReturnValue([self checkIsRunning:error], rv);
-
-    dispatch_sync(_chipWorkQueue, ^{
-        VerifyOrReturn([self checkIsRunning:error]);
+    auto adapter = ^{
         rv = block();
-    });
+    };
+
+    [self syncRunOnWorkQueue:adapter error:error];
 
     return rv;
 }
@@ -796,13 +787,10 @@
 - (BOOL)syncRunOnWorkQueueWithBoolReturnValue:(SyncWorkQueueBlockWithBoolReturnValue)block error:(NSError * __autoreleasing *)error
 {
     __block BOOL success = NO;
-
-    VerifyOrReturnValue([self checkIsRunning:error], success);
-
-    dispatch_sync(_chipWorkQueue, ^{
-        VerifyOrReturn([self checkIsRunning:error]);
+    auto adapter = ^{
         success = block();
-    });
+    };
+    [self syncRunOnWorkQueue:adapter error:error];
 
     return success;
 }
@@ -1001,22 +989,24 @@
 
     // We know getSessionForNode will return YES here, since we already checked
     // that we are running.
-    return [self getSessionForNode:deviceID
-                        completion:^(chip::Messaging::ExchangeManager * _Nullable exchangeManager,
-                            const chip::Optional<chip::SessionHandle> & session, NSError * _Nullable error) {
-                            // Create an MTRBaseDevice for the node id involved, now that our
-                            // CASE session is primed.  We don't actually care about the session
-                            // information here.
-                            dispatch_async(queue, ^{
-                                MTRBaseDevice * device;
-                                if (error == nil) {
-                                    device = [[MTRBaseDevice alloc] initWithNodeID:@(deviceID) controller:self];
-                                } else {
-                                    device = nil;
-                                }
-                                completion(device, error);
-                            });
-                        }];
+    [self getSessionForNode:deviceID
+                 completion:^(chip::Messaging::ExchangeManager * _Nullable exchangeManager,
+                     const chip::Optional<chip::SessionHandle> & session, NSError * _Nullable error) {
+                     // Create an MTRBaseDevice for the node id involved, now that our
+                     // CASE session is primed.  We don't actually care about the session
+                     // information here.
+                     dispatch_async(queue, ^{
+                         MTRBaseDevice * device;
+                         if (error == nil) {
+                             device = [[MTRBaseDevice alloc] initWithNodeID:@(deviceID) controller:self];
+                         } else {
+                             device = nil;
+                         }
+                         completion(device, error);
+                     });
+                 }];
+
+    return YES;
 }
 
 - (BOOL)pairDevice:(uint64_t)deviceID
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
index bed9b70..08a31c4 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
@@ -108,31 +108,36 @@
 /**
  * Ensure we have a CASE session to the given node ID and then call the provided
  * connection callback.  This may be called on any queue (including the Matter
- * event queue) and will always call the provided connection callback on the
- * Matter queue, asynchronously.  Consumers must be prepared to run on the
- * Matter queue (an in particular must not use any APIs that will try to do sync
- * dispatch to the Matter queue).
+ * event queue) and on success will always call the provided connection callback
+ * on the Matter queue, asynchronously.  Consumers must be prepared to run on
+ * the Matter queue (an in particular must not use any APIs that will try to do
+ * sync dispatch to the Matter queue).
  *
- * If the controller is not running when this function is called, will return NO
- * and never invoke the completion.  If the controller is not running when the
- * async dispatch on the Matter queue would happen, an error will be dispatched
- * to the completion handler.
+ * If the controller is not running when this function is called, it will
+ * synchronously invoke the completion with an error, on whatever queue
+ * getSessionForNode was called on.
+ *
+ * If the controller is not running when the async dispatch on the Matter queue
+ * happens, the completion will be invoked with an error on the Matter queue.
  */
-- (BOOL)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion;
+- (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion;
 
 /**
  * Get a session for the commissionee device with the given device id.  This may
- * be called on any queue (including the Matter event queue) and will always
- * call the provided connection callback on the Matter queue, asynchronously.
- * Consumers must be prepared to run on the Matter queue (an in particular must
- * not use any APIs that will try to do sync dispatch to the Matter queue).
+ * be called on any queue (including the Matter event queue) and on success will
+ * always call the provided connection callback on the Matter queue,
+ * asynchronously.  Consumers must be prepared to run on the Matter queue (an in
+ * particular must not use any APIs that will try to do sync dispatch to the
+ * Matter queue).
  *
- * If the controller is not running when this function is called, will return NO
- * and never invoke the completion.  If the controller is not running when the
- * async dispatch on the Matter queue would happen, an error will be dispatched
- * to the completion handler.
+ * If the controller is not running when this function is called, it will
+ * synchronously invoke the completion with an error, on whatever queue
+ * getSessionForCommissioneeDevice was called on.
+ *
+ * If the controller is not running when the async dispatch on the Matter queue
+ * happens, the completion will be invoked with an error on the Matter queue.
  */
-- (BOOL)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion;
+- (void)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion;
 
 /**
  * Invalidate the CASE session for the given node ID.  This is a temporary thing
@@ -150,9 +155,22 @@
  *
  * The DeviceCommissioner pointer passed to the callback should only be used
  * synchronously during the callback invocation.
+ *
+ * If the error handler is nil, failure to run the block will be silent.
  */
-- (void)asyncDispatchToMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
-                      errorHandler:(void (^)(NSError *))errorHandler;
+- (void)asyncGetCommissionerOnMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
+                             errorHandler:(nullable MTRDeviceErrorHandler)errorHandler;
+
+/**
+ * Try to asynchronously dispatch the given block on the Matter queue.  If the
+ * controller is not running either at call time or when the block would be
+ * about to run, the provided error handler will be called with an error.  Note
+ * that this means the error handler might be called on an arbitrary queue, and
+ * might be called before this function returns or after it returns.
+ *
+ * If the error handler is nil, failure to run the block will be silent.
+ */
+- (void)asyncDispatchToMatterQueue:(dispatch_block_t)block errorHandler:(nullable MTRDeviceErrorHandler)errorHandler;
 
 /**
  * Get an MTRBaseDevice for the given node id.  This exists to allow subclasses
diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm
index d0d6dfc..349c255 100644
--- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm
+++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm
@@ -160,7 +160,7 @@
 
         auto completionHandler = ^(NSError * _Nullable error) {
             [controller
-                asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                asyncDispatchToMatterQueue:^() {
                     assertChipStackLockedByCurrentThread();
 
                     if (!mInitialized || mTransferGeneration != transferGeneration) {
@@ -264,7 +264,7 @@
 
         auto completionHandler = ^(NSData * _Nullable data, BOOL isEOF) {
             [controller
-                asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                asyncDispatchToMatterQueue:^() {
                     assertChipStackLockedByCurrentThread();
 
                     if (!mInitialized || mTransferGeneration != transferGeneration) {
@@ -552,7 +552,7 @@
     auto completionHandler
         = ^(MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) {
               [controller
-                  asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                  asyncDispatchToMatterQueue:^() {
                       assertChipStackLockedByCurrentThread();
 
                       CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "QueryImage", data, error);
@@ -646,7 +646,7 @@
     auto completionHandler
         = ^(MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, NSError * _Nullable error) {
               [controller
-                  asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                  asyncDispatchToMatterQueue:^() {
                       assertChipStackLockedByCurrentThread();
 
                       CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "ApplyUpdateRequest", data, error);
@@ -707,7 +707,7 @@
 
     auto completionHandler = ^(NSError * _Nullable error) {
         [controller
-            asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+            asyncDispatchToMatterQueue:^() {
                 assertChipStackLockedByCurrentThread();
 
                 CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "NotifyUpdateApplied", error);
diff --git a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h
index e0e3413..f31eb18 100644
--- a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h
+++ b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h
@@ -69,8 +69,6 @@
         return mCppCommissioner == nullptr ? chip::NullOptional : mCppCommissioner->GetCommissioningParameters();
     }
 
-    void setChipWorkQueue(dispatch_queue_t chipWorkQueue) { mChipWorkQueue = chipWorkQueue; }
-
     void SetOperationalCertificateIssuer(
         id<MTROperationalCertificateIssuer> operationalCertificateIssuer, dispatch_queue_t operationalCertificateIssuerQueue)
     {
@@ -156,7 +154,6 @@
     chip::Controller::DeviceCommissioner * _Nullable mCppCommissioner = nullptr;
     id<MTROperationalCertificateIssuer> _Nullable mOperationalCertificateIssuer;
     dispatch_queue_t _Nullable mOperationalCertificateIssuerQueue;
-    dispatch_queue_t _Nullable mChipWorkQueue;
     chip::Callback::Callback<chip::Controller::OnNOCChainGeneration> * _Nullable mOnNOCCompletionCallback = nullptr;
 };
 
diff --git a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
index 40e2fd3..59a8fe3 100644
--- a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
+++ b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm
@@ -22,6 +22,7 @@
 #import <Security/Security.h>
 
 #import "MTRCertificates.h"
+#import "MTRDeviceController_Internal.h"
 #import "MTRLogging_Internal.h"
 #import "NSDataSpanConversion.h"
 
@@ -219,54 +220,55 @@
 void MTROperationalCredentialsDelegate::ExternalNOCChainGenerated(
     MTROperationalCertificateInfo * _Nullable info, NSError * _Nullable error)
 {
-    MTRDeviceController * __weak weakController = mWeakController;
-    dispatch_async(mChipWorkQueue, ^{
-        MTRDeviceController * strongController = weakController;
-        if (strongController == nil || !strongController.isRunning) {
-            // No longer safe to touch "this"
-            return;
+    // Dispatch will only happen if the controller is still running, which means we
+    // are safe to touch our members.
+    [mWeakController
+        asyncGetCommissionerOnMatterQueue:^(Controller::DeviceCommissioner * commissioner) {
+            assertChipStackLockedByCurrentThread();
+
+            if (mOnNOCCompletionCallback == nullptr) {
+                return;
+            }
+
+            auto * onCompletion = mOnNOCCompletionCallback;
+            mOnNOCCompletionCallback = nullptr;
+
+            if (mCppCommissioner != commissioner) {
+                // Quite unexpected!
+                return;
+            }
+
+            if (info == nil) {
+                onCompletion->mCall(onCompletion->mContext, [MTRError errorToCHIPErrorCode:error], ByteSpan(), ByteSpan(),
+                    ByteSpan(), NullOptional, NullOptional);
+                return;
+            }
+
+            auto commissioningParameters = commissioner->GetCommissioningParameters();
+            if (!commissioningParameters.HasValue()) {
+                return;
+            }
+
+            AesCcm128KeySpan ipk = commissioningParameters.Value().GetIpk().ValueOr(GetIPK());
+
+            Optional<NodeId> adminSubject;
+            if (info.adminSubject != nil) {
+                adminSubject.SetValue(info.adminSubject.unsignedLongLongValue);
+            } else {
+                adminSubject = commissioningParameters.Value().GetAdminSubject();
+            }
+
+            ByteSpan intermediateCertificate;
+            if (info.intermediateCertificate != nil) {
+                intermediateCertificate = AsByteSpan(info.intermediateCertificate);
+            }
+
+            onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, AsByteSpan(info.operationalCertificate),
+                intermediateCertificate, AsByteSpan(info.rootCertificate), MakeOptional(ipk), adminSubject);
         }
-
-        if (mOnNOCCompletionCallback == nullptr) {
-            return;
-        }
-
-        auto * onCompletion = mOnNOCCompletionCallback;
-        mOnNOCCompletionCallback = nullptr;
-
-        if (mCppCommissioner == nullptr) {
-            // Quite unexpected, since we checked that our controller is running already.
-            return;
-        }
-
-        if (info == nil) {
-            onCompletion->mCall(onCompletion->mContext, [MTRError errorToCHIPErrorCode:error], ByteSpan(), ByteSpan(), ByteSpan(),
-                NullOptional, NullOptional);
-            return;
-        }
-
-        auto commissioningParameters = mCppCommissioner->GetCommissioningParameters();
-        if (!commissioningParameters.HasValue()) {
-            return;
-        }
-
-        AesCcm128KeySpan ipk = commissioningParameters.Value().GetIpk().ValueOr(GetIPK());
-
-        Optional<NodeId> adminSubject;
-        if (info.adminSubject != nil) {
-            adminSubject.SetValue(info.adminSubject.unsignedLongLongValue);
-        } else {
-            adminSubject = commissioningParameters.Value().GetAdminSubject();
-        }
-
-        ByteSpan intermediateCertificate;
-        if (info.intermediateCertificate != nil) {
-            intermediateCertificate = AsByteSpan(info.intermediateCertificate);
-        }
-
-        onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, AsByteSpan(info.operationalCertificate), intermediateCertificate,
-            AsByteSpan(info.rootCertificate), MakeOptional(ipk), adminSubject);
-    });
+                             // If we can't run the block, we're torn down and should
+                             // just do nothing.
+                             errorHandler:nil];
 }
 
 CHIP_ERROR MTROperationalCredentialsDelegate::LocalGenerateNOCChain(const chip::ByteSpan & csrElements,