Repository navigation
fix(redis): close the channel when the server is gone (celery/celery#11054) - #2839
Draft
aurangzaib048 wants to merge 1 commit into
Draft
aurangzaib048 wants to merge 1 commit into
aurangzaib048 wants to merge 1 commit into
Conversation
…11054) Channel.close() drains an in-flight BRPOP and deletes auto-delete fanout bindings before it closes the channel. Both need the server, and a connection error from either left the channel open. Connection.collect() swallows that error and then detaches the transport, so the next close() of the same channel, once the server was back, restored unacked messages through a transport whose client is None and raised AttributeError. A celery worker calls collect() and then close() after a connection error, so it died on a Redis restart while a prefetched task was waiting. Skip the drain and the fanout cleanup on a connection error and finish closing the channel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes celery/celery#11054.
A celery 5.7 worker on the Redis broker exits with
AttributeError: 'NoneType' object has no attribute 'hostname'when Redis restarts while a prefetched task is waiting in the worker. The RedisChannel.close()could stop part-way when the server was gone, and the channel then stayed open until a later close, which ran afterConnection.collect()had detached the transport it needs.Root cause
Before it closes the channel,
Channel.close()drains a BRPOP that is in flight and deletes the auto-delete fanout bindings. Both need the server. A connection error from either propagated out ofclose()beforesuper().close(), so the channel stayed open (closedisFalse) with its unacked deliveries.Connection.collect()swallows that error, inmaybe_close_channel()and aroundclose_connection(), and then_do_close_transport()setstransport.client = None. The channel is still the connection's_default_channel, so the nextConnection.close()closes it again. If the server is back by then, that close gets as far asrestore_unacked_once(), which builds a connection pool fromtransport.clientand raisesAttributeError. That is neither a connection nor a channel error, so nothing ignores it.Since celery 5.7 (celery/celery#10242) the worker calls
collect()and thenclose()after a connection error, so a Redis restart with a prefetched task waiting killed the worker. In a run with a logging wrapper aroundmaybe_close_channel(), the first close insidecollect()failed in the BRPOP drain, and the second one, fromclose_connection(), in the fanoutqueue_delete()the reporter found.Changes
kombu/transport/redis.py:Channel.close()skips the drain and the fanout binding cleanup on a connection error, then disconnects its pools and clients and closes the channel as before. Unacked deliveries are restored at that point when the server is reachable; when it is not, they stay in theunackedhash untilvisibility_timeout, as they did before.t/unit/transport/test_redis.py: a close whose drain fails still closes the channel, and the 5.7 regression: worker dies with AttributeError ('NoneType' object has no attribute 'hostname') when the Redis broker restarts while a prefetched task waits celery#11054 sequence (fanout cleanup fails while the server is down duringcollect(), thenclose()once it is back) restores unacked messages only while the transport is attached.Test plan
main(the drain error escapesclose(); unacked messages are restored through a transport whoseclientisNone) and pass with the fixmain(1a48583d4) prefork-c 2, three 8 s tasks so one waits prefetched,docker restart -t 0of aredis:7.2 --appendonly yescontainer in the worker's network namespace, then a ping task; redis-py 6.4.0main: the worker exits with theAttributeErroron the first restartmainlogsError on restarting Gossip: AttributeError(...)on all 15 (the Gossip channel's fanout cleanup fails the same way) and this branch on noneacks_late,-c 2, three 6 s tasks, Redis restarted 2 s in), 8 runs each: onmainthe worker exits in all 8 when a task that was running acks through the closed channel (_connparams()on aNonetransport.client); on this branch none exit and both finished tasks are removed fromunacked. The close insidecollect()now completes while the transport is attached, and its restore attempt leaves a pool the late ack uses. WithCLIENT KILLinstead of a restart both survive, with the same 2 redeliveries per run from the restore at close3.14-unitand3.12-unitas an unprivileged user, 2342 passed each; macOS (3.12) 2228 passed3.14-linux-integration-*, CI's pytest command): py-amqp 51, redis 129, mongodb 26, kafka 38, sqs 42, pgmq 29 passed