@@ -35,15 +35,123 @@ def value(self, value):
3535 self ._value = value
3636
3737
38+ class _DeferredNotifier :
39+ """Queues the watcher notifications raised inside a locked section and delivers them
40+ once the lock has been released.
41+
42+ Watchers have a lock of their own, and some of them (the redis watcher, for one) hold
43+ it while running the update callback, which calls back into the enforcer to reload the
44+ policy. Notifying from inside the enforcer lock would let the two threads take those
45+ two locks in opposite orders and deadlock:
46+ https://github.com/casbin/pycasbin/issues/408
47+ """
48+
49+ def __init__ (self ):
50+ self ._local = threading .local ()
51+
52+ def _stack (self ):
53+ stack = getattr (self ._local , "stack" , None )
54+ if stack is None :
55+ stack = []
56+ self ._local .stack = stack
57+ return stack
58+
59+ def defer (self , func , args , kwargs ):
60+ """queues a call, and returns whether it was queued. False means no lock is held
61+ by this thread, so the caller has to run the call itself."""
62+ stack = self ._stack ()
63+ if not stack :
64+ return False
65+ stack [- 1 ].append ((func , args , kwargs ))
66+ return True
67+
68+ def begin (self ):
69+ """starts collecting, called right after the lock has been taken."""
70+ self ._stack ().append ([])
71+
72+ def end (self , suppress_errors = False ):
73+ """delivers what was collected, called right after the lock has been released."""
74+ stack = self ._stack ()
75+ queued = stack .pop ()
76+ if stack :
77+ # an outer lock is still held, so hand the calls over to it
78+ stack [- 1 ].extend (queued )
79+ return
80+
81+ for func , args , kwargs in queued :
82+ try :
83+ func (* args , ** kwargs )
84+ except Exception :
85+ # the caller is already unwinding an exception of its own, and hiding it
86+ # behind a notification failure would only make the original harder to find
87+ if not suppress_errors :
88+ raise
89+
90+
91+ class _NotifyAfterUnlock :
92+ """wraps one of the enforcer locks so that _DeferredNotifier collects notifications
93+ for as long as the lock is held."""
94+
95+ def __init__ (self , lock , notifier ):
96+ self ._lock = lock
97+ self ._notifier = notifier
98+
99+ def __enter__ (self ):
100+ self ._lock .__enter__ ()
101+ self ._notifier .begin ()
102+ return self
103+
104+ def __exit__ (self , exc_type , exc_value , traceback ):
105+ try :
106+ self ._lock .__exit__ (exc_type , exc_value , traceback )
107+ finally :
108+ self ._notifier .end (suppress_errors = exc_type is not None )
109+ return False
110+
111+
112+ class _DeferredWatcher :
113+ """stands in for the user's watcher on the wrapped enforcer, and routes every call it
114+ makes through a _DeferredNotifier."""
115+
116+ def __init__ (self , watcher , notifier ):
117+ self ._watcher = watcher
118+ self ._notifier = notifier
119+
120+ def __bool__ (self ):
121+ return bool (self ._watcher )
122+
123+ def __getattr__ (self , name ):
124+ watcher = self .__dict__ .get ("_watcher" )
125+ if watcher is None :
126+ raise AttributeError (name )
127+
128+ attr = getattr (watcher , name )
129+ if not callable (attr ):
130+ return attr
131+
132+ notifier = self ._notifier
133+
134+ def deferred (* args , ** kwargs ):
135+ # a deferred call has no result to hand back yet, but the enforcer ignores
136+ # what the watcher returns anyway
137+ if notifier .defer (attr , args , kwargs ):
138+ return None
139+ return attr (* args , ** kwargs )
140+
141+ return deferred
142+
143+
38144class SyncedEnforcer :
39145 """SyncedEnforcer wraps Enforcer and provides synchronized access.
40146 It's also a drop-in replacement for Enforcer"""
41147
42148 def __init__ (self , model = None , adapter = None ):
43149 self ._e = self ._new_enforcer (model , adapter )
44150 self ._rwlock = RWLockWrite ()
45- self ._rl = self ._rwlock .gen_rlock ()
46- self ._wl = self ._rwlock .gen_wlock ()
151+ self ._notifier = _DeferredNotifier ()
152+ self ._rl = _NotifyAfterUnlock (self ._rwlock .gen_rlock (), self ._notifier )
153+ self ._wl = _NotifyAfterUnlock (self ._rwlock .gen_wlock (), self ._notifier )
154+ self ._watcher = None
47155 self ._auto_loading = AtomicBool (False )
48156 self ._auto_loading_thread = None
49157
@@ -115,9 +223,19 @@ def set_adapter(self, adapter):
115223 self ._e .set_adapter (adapter )
116224
117225 def set_watcher (self , watcher ):
118- """sets the current watcher."""
226+ """sets the current watcher.
227+
228+ It is notified only once the enforcer lock has been released, so a watcher that
229+ holds a lock of its own while running the update callback cannot deadlock against
230+ the enforcer."""
119231 with self ._wl :
120- self ._e .set_watcher (watcher )
232+ self ._watcher = watcher
233+ self ._e .set_watcher (None if watcher is None else _DeferredWatcher (watcher , self ._notifier ))
234+
235+ def get_watcher (self ):
236+ """gets the watcher that was passed to set_watcher()."""
237+ with self ._rl :
238+ return self ._watcher
121239
122240 def set_effector (self , eft ):
123241 """sets the current effector."""
0 commit comments