mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #21952 -- signals deadlock due to locking + weakref interaction
This commit is contained in:
		
				
					committed by
					
						 Florian Apolloner
						Florian Apolloner
					
				
			
			
				
	
			
			
			
						parent
						
							aea9faa146
						
					
				
				
					commit
					c29d6f7676
				
			| @@ -48,6 +48,7 @@ class Signal(object): | |||||||
|         # 'sender_receivers_cache'. The cache is cleaned when .connect() or |         # 'sender_receivers_cache'. The cache is cleaned when .connect() or | ||||||
|         # .disconnect() is called and populated on send(). |         # .disconnect() is called and populated on send(). | ||||||
|         self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {} |         self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {} | ||||||
|  |         self._dead_receivers = False | ||||||
|  |  | ||||||
|     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): |     def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): | ||||||
|         """ |         """ | ||||||
| @@ -127,6 +128,7 @@ class Signal(object): | |||||||
|                 receiver_id = _make_id(receiver) |                 receiver_id = _make_id(receiver) | ||||||
|  |  | ||||||
|         with self.lock: |         with self.lock: | ||||||
|  |             self._clear_dead_receivers() | ||||||
|             for r_key, _, _ in self.receivers: |             for r_key, _, _ in self.receivers: | ||||||
|                 if r_key == lookup_key: |                 if r_key == lookup_key: | ||||||
|                     break |                     break | ||||||
| @@ -162,6 +164,7 @@ class Signal(object): | |||||||
|             lookup_key = (_make_id(receiver), _make_id(sender)) |             lookup_key = (_make_id(receiver), _make_id(sender)) | ||||||
|  |  | ||||||
|         with self.lock: |         with self.lock: | ||||||
|  |             self._clear_dead_receivers() | ||||||
|             for index in xrange(len(self.receivers)): |             for index in xrange(len(self.receivers)): | ||||||
|                 (r_key, _, _) = self.receivers[index] |                 (r_key, _, _) = self.receivers[index] | ||||||
|                 if r_key == lookup_key: |                 if r_key == lookup_key: | ||||||
| @@ -237,6 +240,17 @@ class Signal(object): | |||||||
|                 responses.append((receiver, response)) |                 responses.append((receiver, response)) | ||||||
|         return responses |         return responses | ||||||
|  |  | ||||||
|  |     def _clear_dead_receivers(self): | ||||||
|  |         # Note: caller is assumed to hold self.lock. | ||||||
|  |         if self._dead_receivers: | ||||||
|  |             self._dead_receivers = False | ||||||
|  |             new_receivers = [] | ||||||
|  |             for r in self.receivers: | ||||||
|  |                 if isinstance(r[1], weakref.ReferenceType) and r[1]() is None: | ||||||
|  |                     continue | ||||||
|  |                 new_receivers.append(r) | ||||||
|  |             self.receivers = new_receivers | ||||||
|  |  | ||||||
|     def _live_receivers(self, sender): |     def _live_receivers(self, sender): | ||||||
|         """ |         """ | ||||||
|         Filter sequence of receivers to get resolved, live receivers. |         Filter sequence of receivers to get resolved, live receivers. | ||||||
| @@ -245,7 +259,7 @@ class Signal(object): | |||||||
|         live receivers. |         live receivers. | ||||||
|         """ |         """ | ||||||
|         receivers = None |         receivers = None | ||||||
|         if self.use_caching: |         if self.use_caching and not self._dead_receivers: | ||||||
|             receivers = self.sender_receivers_cache.get(sender) |             receivers = self.sender_receivers_cache.get(sender) | ||||||
|             # We could end up here with NO_RECEIVERS even if we do check this case in |             # We could end up here with NO_RECEIVERS even if we do check this case in | ||||||
|             # .send() prior to calling _live_receivers() due to concurrent .send() call. |             # .send() prior to calling _live_receivers() due to concurrent .send() call. | ||||||
| @@ -253,6 +267,7 @@ class Signal(object): | |||||||
|                 return [] |                 return [] | ||||||
|         if receivers is None: |         if receivers is None: | ||||||
|             with self.lock: |             with self.lock: | ||||||
|  |                 self._clear_dead_receivers() | ||||||
|                 senderkey = _make_id(sender) |                 senderkey = _make_id(sender) | ||||||
|                 receivers = [] |                 receivers = [] | ||||||
|                 for (receiverkey, r_senderkey), receiver, _ in self.receivers: |                 for (receiverkey, r_senderkey), receiver, _ in self.receivers: | ||||||
| @@ -276,19 +291,13 @@ class Signal(object): | |||||||
|         return non_weak_receivers |         return non_weak_receivers | ||||||
|  |  | ||||||
|     def _remove_receiver(self, receiver=None, receiver_id=None, _make_id=_make_id): |     def _remove_receiver(self, receiver=None, receiver_id=None, _make_id=_make_id): | ||||||
|         """ |         # Mark that the self.receivers list has dead weakrefs. If so, we will | ||||||
|         Remove dead receivers from connections. |         # clean those up in connect, disconnect and _live_receivers while | ||||||
|  |         # holding self.lock. Note that doing the cleanup here isn't a good | ||||||
|         `receiver_id` is used by python 3.4 and up. `receiver` is used in older |         # idea, _remove_receiver() will be called as side effect of garbage | ||||||
|         versions and is the weakref to the receiver (if the connection was defined |         # collection, and so the call can happen while we are already holding | ||||||
|         as `weak`). We also need to pass on `_make_id` since the original reference |         # self.lock. | ||||||
|         will be None during module shutdown. |         self._dead_receivers = True | ||||||
|         """ |  | ||||||
|         with self.lock: |  | ||||||
|             if receiver is not None: |  | ||||||
|                 receiver_id = _make_id(receiver) |  | ||||||
|             self.receivers[:] = [val for val in self.receivers if val[2] != receiver_id] |  | ||||||
|             self.sender_receivers_cache.clear() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def receiver(signal, **kwargs): | def receiver(signal, **kwargs): | ||||||
|   | |||||||
| @@ -46,11 +46,12 @@ class DispatcherTests(unittest.TestCase): | |||||||
|  |  | ||||||
|     def _testIsClean(self, signal): |     def _testIsClean(self, signal): | ||||||
|         """Assert that everything has been cleaned up automatically""" |         """Assert that everything has been cleaned up automatically""" | ||||||
|  |         # Note that dead weakref cleanup happens as side effect of using | ||||||
|  |         # the signal's receivers through the signals API. So, first do a | ||||||
|  |         # call to an API method to force cleanup. | ||||||
|  |         self.assertFalse(signal.has_listeners()) | ||||||
|         self.assertEqual(signal.receivers, []) |         self.assertEqual(signal.receivers, []) | ||||||
|  |  | ||||||
|         # force cleanup just in case |  | ||||||
|         signal.receivers = [] |  | ||||||
|  |  | ||||||
|     def testExact(self): |     def testExact(self): | ||||||
|         a_signal.connect(receiver_1_arg, sender=self) |         a_signal.connect(receiver_1_arg, sender=self) | ||||||
|         expected = [(receiver_1_arg, "test")] |         expected = [(receiver_1_arg, "test")] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user