@@ -45,6 +45,20 @@ func (o OAuth2Options) Clone() *OAuth2Options {
4545
4646}
4747
48+ // TopologyRecoveryOptions is used to configure the topology recovery behavior of the connection.
49+ // See [TopologyRecoveryDisabled], [TopologyRecoveryOnlyTransient], and [TopologyRecoveryAllEnabled] for more information.
50+ type TopologyRecoveryOptions byte
51+
52+ const (
53+ // TopologyRecoveryOnlyTransient recovers only queues declared as exclusive and/or auto delete, and
54+ // related bindings. Exchanges are not recovered.
55+ TopologyRecoveryOnlyTransient TopologyRecoveryOptions = iota
56+ // TopologyRecoveryDisabled disables the topology recovery.
57+ TopologyRecoveryDisabled
58+ // TopologyRecoveryAllEnabled recovers all the topology. All exchanges, queues, and bindings are recovered.
59+ TopologyRecoveryAllEnabled
60+ )
61+
4862type AmqpConnOptions struct {
4963 // wrapper for amqp.ConnOptions
5064 ContainerID string
@@ -76,6 +90,9 @@ type AmqpConnOptions struct {
7690 // when the connection is closed unexpectedly.
7791 RecoveryConfiguration * RecoveryConfiguration
7892
93+ // TopologyRecoveryOptions is used to configure the topology recovery behavior of the connection.
94+ TopologyRecoveryOptions TopologyRecoveryOptions
95+
7996 // The OAuth2Options is used to configure the connection with OAuth2 token.
8097 OAuth2Options * OAuth2Options
8198
@@ -91,15 +108,16 @@ func (a *AmqpConnOptions) isOAuth2() bool {
91108func (a * AmqpConnOptions ) Clone () * AmqpConnOptions {
92109
93110 cloned := & AmqpConnOptions {
94- ContainerID : a .ContainerID ,
95- IdleTimeout : a .IdleTimeout ,
96- MaxFrameSize : a .MaxFrameSize ,
97- MaxSessions : a .MaxSessions ,
98- Properties : a .Properties ,
99- SASLType : a .SASLType ,
100- TLSConfig : a .TLSConfig ,
101- WriteTimeout : a .WriteTimeout ,
102- Id : a .Id ,
111+ ContainerID : a .ContainerID ,
112+ IdleTimeout : a .IdleTimeout ,
113+ MaxFrameSize : a .MaxFrameSize ,
114+ MaxSessions : a .MaxSessions ,
115+ Properties : a .Properties ,
116+ SASLType : a .SASLType ,
117+ TLSConfig : a .TLSConfig ,
118+ WriteTimeout : a .WriteTimeout ,
119+ Id : a .Id ,
120+ TopologyRecoveryOptions : a .TopologyRecoveryOptions ,
103121 }
104122 if a .OAuth2Options != nil {
105123 cloned .OAuth2Options = a .OAuth2Options .Clone ()
@@ -109,23 +127,23 @@ func (a *AmqpConnOptions) Clone() *AmqpConnOptions {
109127 }
110128
111129 return cloned
112-
113130}
114131
115132type AmqpConnection struct {
116133 properties map [string ]any
117134 featuresAvailable * featuresAvailable
118135
119- azureConnection * amqp.Conn
120- management * AmqpManagement
121- lifeCycle * LifeCycle
122- amqpConnOptions * AmqpConnOptions
123- address string
124- session * amqp.Session
125- refMap * sync.Map
126- entitiesTracker * entitiesTracker
127- mutex sync.RWMutex
128- closed bool
136+ azureConnection * amqp.Conn
137+ management * AmqpManagement
138+ lifeCycle * LifeCycle
139+ amqpConnOptions * AmqpConnOptions
140+ address string
141+ session * amqp.Session
142+ refMap * sync.Map
143+ entitiesTracker * entitiesTracker
144+ topologyRecoveryRecords * topologyRecoveryRecords
145+ mutex sync.RWMutex
146+ closed bool
129147}
130148
131149func (a * AmqpConnection ) Properties () map [string ]any {
@@ -321,16 +339,19 @@ func Dial(ctx context.Context, address string, connOptions *AmqpConnOptions) (*A
321339 if err != nil {
322340 return nil , err
323341 }
324-
325342 // create the connection
326343 conn := & AmqpConnection {
327- management : newAmqpManagement (),
328- lifeCycle : NewLifeCycle (),
329- amqpConnOptions : connOptions ,
330- entitiesTracker : newEntitiesTracker (),
331- featuresAvailable : newFeaturesAvailable (),
344+ management : newAmqpManagement (connOptions .TopologyRecoveryOptions ),
345+ lifeCycle : NewLifeCycle (),
346+ amqpConnOptions : connOptions ,
347+ entitiesTracker : newEntitiesTracker (),
348+ topologyRecoveryRecords : newTopologyRecoveryRecords (),
349+ featuresAvailable : newFeaturesAvailable (),
332350 }
333351
352+ // management needs to access the connection to manage the recovery records
353+ conn .management .topologyRecoveryRecords = conn .topologyRecoveryRecords
354+
334355 err = conn .open (ctx , address , connOptions )
335356 if err != nil {
336357 return nil , err
@@ -496,6 +517,7 @@ func (a *AmqpConnection) maybeReconnect() {
496517 cancel ()
497518
498519 if err == nil {
520+ a .recoverTopology ()
499521 a .restartEntities ()
500522 a .lifeCycle .SetState (& StateOpen {})
501523 return
@@ -551,6 +573,39 @@ func (a *AmqpConnection) restartEntities() {
551573 "consumerFails" , consumerFails )
552574}
553575
576+ func (a * AmqpConnection ) recoverTopology () {
577+ Debug ("Recovering topology" )
578+ // Set the isRecovering flag to prevent duplicate recovery records.
579+ // Using atomic operations since this runs in the recovery goroutine
580+ // while public API methods can be called from user goroutines.
581+ a .management .isRecovering .Store (true )
582+ defer func () {
583+ a .management .isRecovering .Store (false )
584+ }()
585+
586+ for _ , exchange := range a .topologyRecoveryRecords .exchanges {
587+ Debug ("Recovering exchange" , "exchange" , exchange .exchangeName )
588+ _ , err := a .Management ().DeclareExchange (context .Background (), exchange .toIExchangeSpecification ())
589+ if err != nil {
590+ Error ("Failed to recover exchange" , "error" , err , "ID" , a .Id (), "exchange" , exchange .exchangeName )
591+ }
592+ }
593+ for _ , queue := range a .topologyRecoveryRecords .queues {
594+ Debug ("Recovering queue" , "queue" , queue .queueName )
595+ _ , err := a .Management ().DeclareQueue (context .Background (), queue .toIQueueSpecification ())
596+ if err != nil {
597+ Error ("Failed to recover queue" , "error" , err , "ID" , a .Id (), "queue" , queue .queueName )
598+ }
599+ }
600+ for _ , binding := range a .topologyRecoveryRecords .bindings {
601+ Debug ("Recovering binding" , "bind source" , binding .sourceExchange , "bind destination" , binding .destination )
602+ _ , err := a .Management ().Bind (context .Background (), binding .toIBindingSpecification ())
603+ if err != nil {
604+ Error ("Failed to recover binding" , "error" , err , "ID" , a .Id (), "bind source" , binding .sourceExchange , "bind destination" , binding .destination )
605+ }
606+ }
607+ }
608+
554609func (a * AmqpConnection ) close () {
555610 if a .refMap != nil {
556611 a .refMap .Delete (a .Id ())
0 commit comments