@@ -136,7 +136,7 @@ class Manhole(_ORIGINAL_THREAD):
136
136
bind_delay (float): Seconds to delay socket binding. Default: `no delay`.
137
137
"""
138
138
139
- def __init__ (self , sigmask , start_timeout , bind_delay = None ):
139
+ def __init__ (self , sigmask , start_timeout , bind_delay = None , locals = None ):
140
140
super (Manhole , self ).__init__ ()
141
141
self .daemon = True
142
142
self .name = "Manhole"
@@ -146,6 +146,7 @@ def __init__(self, sigmask, start_timeout, bind_delay=None):
146
146
# see: http://emptysqua.re/blog/dawn-of-the-thread/
147
147
self .start_timeout = start_timeout
148
148
self .bind_delay = bind_delay
149
+ self .locals = locals
149
150
150
151
def start (self ):
151
152
super (Manhole , self ).start ()
@@ -183,7 +184,7 @@ def run(self):
183
184
while True :
184
185
cry ("Waiting for new connection (in pid:%s) ..." % os .getpid ())
185
186
try :
186
- client = ManholeConnection (sock .accept ()[0 ], self .sigmask )
187
+ client = ManholeConnection (sock .accept ()[0 ], self .sigmask , self . locals )
187
188
client .start ()
188
189
client .join ()
189
190
except (InterruptedError , socket .error ) as e :
@@ -199,12 +200,13 @@ class ManholeConnection(_ORIGINAL_THREAD):
199
200
Manhole thread that handles the connection. This thread is a normal thread (non-daemon) - it won't exit if the
200
201
main thread exits.
201
202
"""
202
- def __init__ (self , client , sigmask ):
203
+ def __init__ (self , client , sigmask , locals ):
203
204
super (ManholeConnection , self ).__init__ ()
204
205
self .daemon = False
205
206
self .client = client
206
207
self .name = "ManholeConnection"
207
208
self .sigmask = sigmask
209
+ self .locals = locals
208
210
209
211
def run (self ):
210
212
cry ('Started ManholeConnection thread. Checking credentials ...' )
@@ -214,7 +216,7 @@ def run(self):
214
216
215
217
pid , _ , _ = self .check_credentials (self .client )
216
218
pthread_setname_np (self .ident , "Manhole %s" % pid )
217
- self .handle (self .client )
219
+ self .handle (self .client , self . locals )
218
220
219
221
@staticmethod
220
222
def check_credentials (client ):
@@ -234,7 +236,7 @@ def check_credentials(client):
234
236
return pid , uid , gid
235
237
236
238
@staticmethod
237
- def handle (client ):
239
+ def handle (client , locals ):
238
240
"""
239
241
Handles connection. This is a static method so it can be used without a thread (eg: from a signal handler -
240
242
`oneshot_on`).
@@ -265,7 +267,7 @@ def handle(client):
265
267
for name in names :
266
268
backup .append ((name , getattr (sys , name )))
267
269
setattr (sys , name , _ORIGINAL_FDOPEN (client_fd , mode , 1 if PY3 else 0 ))
268
- run_repl ()
270
+ run_repl (locals )
269
271
cry ("DONE." )
270
272
finally :
271
273
try :
@@ -294,27 +296,31 @@ def handle(client):
294
296
cry (traceback .format_exc ())
295
297
296
298
297
- def run_repl ():
299
+ def run_repl (locals ):
298
300
"""
299
301
Dumps stacktraces and runs an interactive prompt (REPL).
300
302
"""
301
303
dump_stacktraces ()
302
- code . InteractiveConsole ( {
304
+ namespace = {
303
305
'dump_stacktraces' : dump_stacktraces ,
304
306
'sys' : sys ,
305
307
'os' : os ,
306
308
'socket' : socket ,
307
309
'traceback' : traceback ,
308
- }).interact ()
310
+ }
311
+ if locals :
312
+ namespace .update (locals )
313
+ code .InteractiveConsole (namespace ).interact ()
309
314
310
315
311
316
def _handle_oneshot (_signum , _frame ):
317
+ assert _INST , "Manhole wasn't installed !"
312
318
try :
313
319
sock = Manhole .get_socket ()
314
320
cry ("Waiting for new connection (in pid:%s) ..." % os .getpid ())
315
321
client , _ = sock .accept ()
316
322
ManholeConnection .check_credentials (client )
317
- ManholeConnection .handle (client )
323
+ ManholeConnection .handle (client , _INST . locals )
318
324
except : # pylint: disable=W0702
319
325
# we don't want to let any exception out, it might make the application missbehave
320
326
cry ("Manhole oneshot connection failed:" )
@@ -377,7 +383,7 @@ def _activate_on_signal(_signum, _frame):
377
383
378
384
379
385
def install (verbose = True , patch_fork = True , activate_on = None , sigmask = ALL_SIGNALS , oneshot_on = None , start_timeout = 0.5 ,
380
- socket_path = None , reinstall_bind_delay = 0.5 ):
386
+ socket_path = None , reinstall_bind_delay = 0.5 , locals = None ):
381
387
"""
382
388
Installs the manhole.
383
389
@@ -398,6 +404,7 @@ def install(verbose=True, patch_fork=True, activate_on=None, sigmask=ALL_SIGNALS
398
404
disables ``patch_fork`` as children cannot resuse the same path.
399
405
reinstall_bind_delay(float): Delay the unix domain socket creation *reinstall_bind_delay* seconds. This alleviates
400
406
cleanup failures when using fork+exec patterns.
407
+ locals(dict): names to add to manhole interactive shell locals.
401
408
"""
402
409
global _STDERR , _INST , _SHOULD_RESTART # pylint: disable=W0603
403
410
global VERBOSE , START_TIMEOUT , SOCKET_PATH , REINSTALL_BIND_DELAY # pylint: disable=W0603
@@ -408,7 +415,7 @@ def install(verbose=True, patch_fork=True, activate_on=None, sigmask=ALL_SIGNALS
408
415
SOCKET_PATH = socket_path
409
416
_STDERR = sys .__stderr__
410
417
if not _INST :
411
- _INST = Manhole (sigmask , start_timeout )
418
+ _INST = Manhole (sigmask , start_timeout , locals = locals )
412
419
if oneshot_on is not None :
413
420
oneshot_on = getattr (signal , 'SIG' + oneshot_on ) if isinstance (oneshot_on , string ) else oneshot_on
414
421
signal .signal (oneshot_on , _handle_oneshot )
@@ -444,7 +451,7 @@ def reinstall():
444
451
assert _INST
445
452
with _INST_LOCK :
446
453
if not (_INST .is_alive () and _INST in _ORIGINAL__ACTIVE ):
447
- _INST = Manhole (_INST .sigmask , START_TIMEOUT , bind_delay = REINSTALL_BIND_DELAY )
454
+ _INST = Manhole (_INST .sigmask , START_TIMEOUT , bind_delay = REINSTALL_BIND_DELAY , locals = _INST . locals )
448
455
if _SHOULD_RESTART :
449
456
_INST .start ()
450
457
0 commit comments