2525# Where host and port configure the network address on which the monitor
2626# operates.
2727#
28- # To connect to the monitor, run python3 -m curio.monitor -H [host] -p [port]. For example:
28+ # To connect to the monitor, run python3 -m curio.monitor -H [host] -p [port].
2929#
3030# Theory of operation:
3131# --------------------
3232# The monitor works by opening up a loopback socket on the local
33- # machine and allowing connections via telnet. By default, it only
34- # allows a connection originating from the local machine. Only a
35- # single monitor connection is allowed at any given time.
33+ # machine and allowing connections via a tool like telnet or nc. By
34+ # default, it only allows a connection originating from the local
35+ # machine. Only a single monitor connection is allowed at any given
36+ # time.
3637#
37- # There are two parts to the monitor itself: a user interface and an
38- # internal loop that runs on curio itself. The user interface part
39- # runs in a completely separate execution thread. The reason for this
40- # is that it allows curio to be monitored even if the curio kernel is
41- # completely deadlocked, occupied with a large CPU-bound task, or
42- # otherwise hosed in the some way. At a minimum, you can connect,
43- # look at the task table, and see what the tasks are doing.
44- #
45- # The internal monitor loop implemented on curio itself is presently
46- # used to implement external task cancellation. Manipulating any part
47- # of the kernel state or task status is unsafe from an outside thread.
48- # To make it safe, the user-interface thread of the monitor hands over
49- # requests requiring the involvement of the kernel to the monitor
50- # loop. Since this loop runs on curio, it can safely make
51- # cancellation requests and perform other kernel-related actions.
38+ # The monitor is implemented externally to Curio using threads. The
39+ # reason for this is that it allows curio to be monitored even if the
40+ # curio kernel is completely deadlocked, occupied with a large
41+ # CPU-bound task, or otherwise hosed in the some way. At a minimum,
42+ # you can connect, look at the task table, and see what the tasks are
43+ # doing.
5244
5345import os
5446import signal
5547import time
5648import socket
5749import threading
58- import telnetlib
5950import argparse
6051import logging
6152import sys
@@ -121,7 +112,6 @@ class Monitor(object):
121112 def __init__ (self , kern , host = MONITOR_HOST , port = MONITOR_PORT ):
122113 self .kernel = kern
123114 self .address = (host , port )
124- self .monitor_queue = queue .UniversalQueue ()
125115 self ._closing = None
126116 self ._ui_thread = None
127117
@@ -131,35 +121,22 @@ def close(self):
131121 if self ._ui_thread :
132122 self ._ui_thread .join ()
133123
134- async def monitor_task (self ):
135- '''
136- Asynchronous task loop for carrying out task cancellation.
137- '''
138- while True :
139- task = await self .monitor_queue .get ()
140- await task .cancel ()
141-
142- async def start (self ):
124+ def start (self ):
143125 '''
144126 Function to start the monitor
145127 '''
146- # The monitor launches both a separate thread and helper task
147- # that runs inside curio itself to manage cancellation events
148-
149128 log .info ('Starting Curio monitor at %s' , self .address )
150-
129+ self . _closing = threading . Event ()
151130 self ._ui_thread = threading .Thread (target = self .server , args = (), daemon = True )
152- self ._closing = threading .Event ()
153131 self ._ui_thread .start ()
154- await spawn (self .monitor_task , daemon = True )
155132
156133 def server (self ):
157134 '''
158135 Synchronous kernel for the monitor. This runs in a separate thread
159136 from curio itself.
160137 '''
161138 sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
162- sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
139+ sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , True )
163140
164141 # set the timeout to prevent the server loop from
165142 # blocking indefinitaly on sock.accept()
@@ -172,7 +149,6 @@ def server(self):
172149 client , addr = sock .accept ()
173150 with client :
174151 client .settimeout (0.5 )
175-
176152 # This bit of magic is for reading lines of input while still allowing timeouts
177153 # and the ability for the monitor to die when curio exits. See Issue #108.
178154 def readlines ():
@@ -193,6 +169,7 @@ def readlines():
193169
194170 sout = client .makefile ('w' , encoding = 'latin-1' )
195171 self .interactive_loop (sout , readlines ())
172+ sout .close ()
196173 except socket .timeout :
197174 continue
198175
@@ -224,14 +201,6 @@ def interactive_loop(self, sout, input_lines):
224201 self .command_exit (sout )
225202 return
226203
227- elif resp .startswith ('cancel' ):
228- _ , taskid_s = resp .split ()
229- self .command_cancel (sout , int (taskid_s ))
230-
231- elif resp .startswith ('signal' ):
232- _ , signame = resp .split ()
233- self .command_signal (sout , signame )
234-
235204 elif resp .startswith ('w' ):
236205 _ , taskid_s = resp .split ()
237206 self .command_where (sout , int (taskid_s ))
@@ -248,8 +217,6 @@ def command_help(self, sout):
248217 '''Commands:
249218 ps : Show task table
250219 where taskid : Show stack frames for a task
251- cancel taskid : Cancel an indicated task
252- signal signame : Send a Unix signal
253220 parents taskid : List task parents
254221 quit : Leave the monitor
255222''' )
@@ -260,18 +227,6 @@ def command_ps(self, sout):
260227 def command_where (self , sout , taskid ):
261228 where (taskid , self .kernel , sout )
262229
263- def command_signal (self , sout , signame ):
264- if hasattr (signal , signame ):
265- os .kill (os .getpid (), getattr (signal , signame ))
266- else :
267- sout .write ('Unknown signal %s\n ' % signame )
268-
269- def command_cancel (self , sout , taskid ):
270- task = self .kernel ._tasks .get (taskid )
271- if task :
272- sout .write ('Cancelling task %d\n ' % taskid )
273- self .monitor_queue .put (task )
274-
275230 def command_parents (self , sout , taskid ):
276231 while taskid :
277232 task = self .kernel ._tasks .get (taskid )
@@ -282,22 +237,25 @@ def command_parents(self, sout, taskid):
282237 break
283238
284239 def command_exit (self , sout ):
285- sout .write ('Leaving monitor. Hit Ctrl-C to exit \n ' )
240+ sout .write ('Leaving monitor.\n ' )
286241 sout .flush ()
287242
288243def monitor_client (host , port ):
289244 '''
290- Client to connect to the monitor via "telnet"
245+ Client to connect to the monitor via a socket
291246 '''
292- tn = telnetlib .Telnet ()
293- tn .open (host , port , timeout = 0.5 )
294- try :
295- tn .interact ()
296- except KeyboardInterrupt :
297- pass
298- finally :
299- tn .close ()
300-
247+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
248+ sock .connect ((host , port ))
249+ def display (sock ):
250+ while (chunk := sock .recv (1000 )):
251+ sys .stdout .write (chunk .decode ('utf-8' ))
252+ sys .stdout .flush ()
253+ os ._exit (0 )
254+ threading .Thread (target = display , args = [sock ], daemon = True ).start ()
255+ while True :
256+ line = sys .stdin .readline ()
257+ sock .sendall (line .encode ('utf-8' ))
258+ sock .close ()
301259
302260def main ():
303261 parser = argparse .ArgumentParser ("usage: python -m curio.monitor [options]" )
0 commit comments