|
| 1 | +# coding=utf-8 |
| 2 | + |
| 3 | +# borrowed from: https://chromium.googlesource.com/external/googleappengine/python/+/master/lib/cherrypy/cherrypy/process/win32.py |
| 4 | + |
| 5 | + |
| 6 | +"""Windows service. Requires pywin32.""" |
| 7 | +import os |
| 8 | +import win32api |
| 9 | +import win32con |
| 10 | +import win32event |
| 11 | +import win32service |
| 12 | +import win32serviceutil |
| 13 | +from cherrypy.process import wspbus, plugins |
| 14 | + |
| 15 | + |
| 16 | +class ConsoleCtrlHandler(plugins.SimplePlugin): |
| 17 | + """A WSPBus plugin for handling Win32 console events (like Ctrl-C).""" |
| 18 | + |
| 19 | + def __init__(self, bus): |
| 20 | + self.is_set = False |
| 21 | + plugins.SimplePlugin.__init__(self, bus) |
| 22 | + |
| 23 | + def start(self): |
| 24 | + if self.is_set: |
| 25 | + self.bus.log('Handler for console events already set.', level=40) |
| 26 | + return |
| 27 | + |
| 28 | + result = win32api.SetConsoleCtrlHandler(self.handle, 1) |
| 29 | + if result == 0: |
| 30 | + self.bus.log('Could not SetConsoleCtrlHandler (error %r)' % |
| 31 | + win32api.GetLastError(), level=40) |
| 32 | + else: |
| 33 | + self.bus.log('Set handler for console events.', level=40) |
| 34 | + self.is_set = True |
| 35 | + |
| 36 | + def stop(self): |
| 37 | + if not self.is_set: |
| 38 | + self.bus.log('Handler for console events already off.', level=40) |
| 39 | + return |
| 40 | + |
| 41 | + try: |
| 42 | + result = win32api.SetConsoleCtrlHandler(self.handle, 0) |
| 43 | + except ValueError: |
| 44 | + # "ValueError: The object has not been registered" |
| 45 | + result = 1 |
| 46 | + |
| 47 | + if result == 0: |
| 48 | + self.bus.log('Could not remove SetConsoleCtrlHandler (error %r)' % |
| 49 | + win32api.GetLastError(), level=40) |
| 50 | + else: |
| 51 | + self.bus.log('Removed handler for console events.', level=40) |
| 52 | + self.is_set = False |
| 53 | + |
| 54 | + def handle(self, event): |
| 55 | + """Handle console control events (like Ctrl-C).""" |
| 56 | + if event in (win32con.CTRL_C_EVENT, win32con.CTRL_LOGOFF_EVENT, |
| 57 | + win32con.CTRL_BREAK_EVENT, win32con.CTRL_SHUTDOWN_EVENT, |
| 58 | + win32con.CTRL_CLOSE_EVENT): |
| 59 | + self.bus.log('Console event %s: shutting down bus' % event) |
| 60 | + |
| 61 | + # Remove self immediately so repeated Ctrl-C doesn't re-call it. |
| 62 | + try: |
| 63 | + self.stop() |
| 64 | + except ValueError: |
| 65 | + pass |
| 66 | + |
| 67 | + self.bus.exit() |
| 68 | + # 'First to return True stops the calls' |
| 69 | + return 1 |
| 70 | + return 0 |
| 71 | + |
| 72 | + |
| 73 | +class Win32Bus(wspbus.Bus): |
| 74 | + """A Web Site Process Bus implementation for Win32. |
| 75 | +
|
| 76 | + Instead of time.sleep, this bus blocks using native win32event objects. |
| 77 | + """ |
| 78 | + |
| 79 | + def __init__(self): |
| 80 | + self.events = {} |
| 81 | + wspbus.Bus.__init__(self) |
| 82 | + |
| 83 | + def _get_state_event(self, state): |
| 84 | + """Return a win32event for the given state (creating it if needed).""" |
| 85 | + try: |
| 86 | + return self.events[state] |
| 87 | + except KeyError: |
| 88 | + event = win32event.CreateEvent(None, 0, 0, |
| 89 | + "WSPBus %s Event (pid=%r)" % |
| 90 | + (state.name, os.getpid())) |
| 91 | + self.events[state] = event |
| 92 | + return event |
| 93 | + |
| 94 | + def _get_state(self): |
| 95 | + return self._state |
| 96 | + |
| 97 | + def _set_state(self, value): |
| 98 | + self._state = value |
| 99 | + event = self._get_state_event(value) |
| 100 | + win32event.PulseEvent(event) |
| 101 | + |
| 102 | + state = property(_get_state, _set_state) |
| 103 | + |
| 104 | + def wait(self, state, interval=0.1, channel=None): |
| 105 | + """Wait for the given state(s), KeyboardInterrupt or SystemExit. |
| 106 | +
|
| 107 | + Since this class uses native win32event objects, the interval |
| 108 | + argument is ignored. |
| 109 | + """ |
| 110 | + if isinstance(state, (tuple, list)): |
| 111 | + # Don't wait for an event that beat us to the punch ;) |
| 112 | + if self.state not in state: |
| 113 | + events = tuple([self._get_state_event(s) for s in state]) |
| 114 | + win32event.WaitForMultipleObjects(events, 0, win32event.INFINITE) |
| 115 | + else: |
| 116 | + # Don't wait for an event that beat us to the punch ;) |
| 117 | + if self.state != state: |
| 118 | + event = self._get_state_event(state) |
| 119 | + win32event.WaitForSingleObject(event, win32event.INFINITE) |
| 120 | + |
| 121 | + |
| 122 | +class _ControlCodes(dict): |
| 123 | + """Control codes used to "signal" a service via ControlService. |
| 124 | +
|
| 125 | + User-defined control codes are in the range 128-255. We generally use |
| 126 | + the standard Python value for the Linux signal and add 128. Example: |
| 127 | +
|
| 128 | + >>> signal.SIGUSR1 |
| 129 | + 10 |
| 130 | + control_codes['graceful'] = 128 + 10 |
| 131 | + """ |
| 132 | + |
| 133 | + def key_for(self, obj): |
| 134 | + """For the given value, return its corresponding key.""" |
| 135 | + for key, val in self.items(): |
| 136 | + if val is obj: |
| 137 | + return key |
| 138 | + raise ValueError("The given object could not be found: %r" % obj) |
| 139 | + |
| 140 | + |
| 141 | +control_codes = _ControlCodes({'graceful': 138}) |
| 142 | + |
| 143 | + |
| 144 | +def signal_child(service, command): |
| 145 | + if command == 'stop': |
| 146 | + win32serviceutil.StopService(service) |
| 147 | + elif command == 'restart': |
| 148 | + win32serviceutil.RestartService(service) |
| 149 | + else: |
| 150 | + win32serviceutil.ControlService(service, control_codes[command]) |
| 151 | + |
| 152 | + |
| 153 | +class PyWebService(win32serviceutil.ServiceFramework): |
| 154 | + """Python Web Service.""" |
| 155 | + |
| 156 | + _svc_name_ = "Python Web Service" |
| 157 | + _svc_display_name_ = "Python Web Service" |
| 158 | + _svc_deps_ = None # sequence of service names on which this depends |
| 159 | + _exe_name_ = "pywebsvc" |
| 160 | + _exe_args_ = None # Default to no arguments |
| 161 | + |
| 162 | + # Only exists on Windows 2000 or later, ignored on windows NT |
| 163 | + _svc_description_ = "Python Web Service" |
| 164 | + |
| 165 | + def SvcDoRun(self): |
| 166 | + from cherrypy import process |
| 167 | + process.bus.start() |
| 168 | + process.bus.block() |
| 169 | + |
| 170 | + def SvcStop(self): |
| 171 | + from cherrypy import process |
| 172 | + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) |
| 173 | + process.bus.exit() |
| 174 | + |
| 175 | + def SvcOther(self, control): |
| 176 | + from cherrypy import process |
| 177 | + process.bus.publish(control_codes.key_for(control)) |
| 178 | + |
| 179 | + |
| 180 | +if __name__ == '__main__': |
| 181 | + win32serviceutil.HandleCommandLine(PyWebService) |
0 commit comments