@@ -376,7 +376,7 @@ def shutdown(self):
376376 self ._mqtt_client .on_disconnect = None
377377 # Now disconnect and do some additional cleanup.
378378 self ._force_transport_disconnect_and_cleanup ()
379- self ._op_manager . cancel_all_operations ()
379+ self .cancel_all_operations ()
380380
381381 def connect (self , password = None ):
382382 """
@@ -480,7 +480,7 @@ def disconnect(self, clear_inflight=False):
480480 )
481481 # Still clear inflight operations since we're effectively disconnected
482482 if clear_inflight :
483- self ._op_manager . cancel_all_operations ()
483+ self .cancel_all_operations ()
484484 else :
485485 # This could result in ConnectionDroppedError or ProtocolClientError
486486 err = _create_error_from_rc_code (rc )
@@ -491,7 +491,21 @@ def disconnect(self, clear_inflight=False):
491491 # cause a force disconnect via the on_disconnect handler, thus it is safe to clear
492492 # ops here and now.
493493 if clear_inflight :
494- self ._op_manager .cancel_all_operations ()
494+ self .cancel_all_operations ()
495+
496+ def cancel_operation (self , callback ):
497+ """Stop tracking the operation associated with a transport callback."""
498+ self ._op_manager .cancel_operation (callback )
499+
500+ def cancel_all_operations (self ):
501+ """Cancel SDK operations and discard corresponding Paho QoS state."""
502+ self ._op_manager .cancel_all_operations ()
503+
504+ # Paho retains QoS 1/2 messages for a future reconnect. Once the SDK has cancelled the
505+ # corresponding operations, those messages must not be retried or kept alive.
506+ with self ._mqtt_client ._out_message_mutex :
507+ self ._mqtt_client ._out_messages .clear ()
508+ self ._mqtt_client ._inflight_messages = 0
495509
496510 def subscribe (self , topic , qos = 1 , callback = None ):
497511 """
@@ -509,7 +523,7 @@ def subscribe(self, topic, qos=1, callback=None):
509523 """
510524 logger .info ("subscribing to {} with qos {}" .format (topic , qos ))
511525 try :
512- ( rc , mid ) = self ._mqtt_client .subscribe (topic , qos = qos )
526+ rc , mid = self ._mqtt_client .subscribe (topic , qos = qos )
513527 except ValueError :
514528 raise
515529 except Exception as e :
@@ -534,7 +548,7 @@ def unsubscribe(self, topic, callback=None):
534548 """
535549 logger .info ("unsubscribing from {}" .format (topic ))
536550 try :
537- ( rc , mid ) = self ._mqtt_client .unsubscribe (topic )
551+ rc , mid = self ._mqtt_client .unsubscribe (topic )
538552 except ValueError :
539553 raise
540554 except Exception as e :
@@ -568,7 +582,7 @@ def publish(self, topic, payload, qos=1, callback=None):
568582 """
569583 logger .info ("publishing on {}" .format (topic ))
570584 try :
571- ( rc , mid ) = self ._mqtt_client .publish (topic = topic , payload = payload , qos = qos )
585+ rc , mid = self ._mqtt_client .publish (topic = topic , payload = payload , qos = qos )
572586 except ValueError :
573587 raise
574588 except TypeError :
@@ -660,9 +674,9 @@ def complete_operation(self, mid):
660674 else :
661675 # Otherwise, store the mid as an unknown response
662676 logger .debug ("Response received for unknown MID: {}" .format (mid ))
663- self ._unknown_operation_completions [
664- mid
665- ] = mid # TODO: set something more useful here
677+ self ._unknown_operation_completions [mid ] = (
678+ mid # TODO: set something more useful here
679+ )
666680
667681 # Now that the lock has been released, if the callback should be triggered,
668682 # go ahead and trigger it now.
@@ -680,6 +694,17 @@ def complete_operation(self, mid):
680694 # fully expected. QOS=1 means we might get 2 PUBACKs
681695 logger .debug ("No callback set for MID: {}" .format (mid ))
682696
697+ def cancel_operation (self , callback ):
698+ """Remove pending operations associated with a callback without invoking it."""
699+ with self ._lock :
700+ matching_mids = [
701+ mid
702+ for mid , pending_callback in self ._pending_operation_callbacks .items ()
703+ if pending_callback is callback
704+ ]
705+ for mid in matching_mids :
706+ del self ._pending_operation_callbacks [mid ]
707+
683708 def cancel_all_operations (self ):
684709 """Complete all pending operations with cancellation, removing MID tracking"""
685710 logger .debug ("Cancelling all pending operations" )
0 commit comments