Skip to content

Commit c2f5c8c

Browse files
committed
First step to allow running server on background. #22
Server is now started on a thread, but there is still now way to avoid stopping it when the server is initialized. All tests pass with this implementation, but test execution time increases nearly 100%. That seems to be solely due to it taking more time (0.6s vs. 0.1s on my setup) to wait for the server process to end after using "Stop Remote Server" keyword. That shouldn't matter at all in normal usage.
1 parent de85cf8 commit c2f5c8c

File tree

1 file changed

+59
-42
lines changed

1 file changed

+59
-42
lines changed

src/robotremoteserver.py

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
from __future__ import print_function
1717

1818
from collections import Mapping
19-
import errno
2019
import inspect
20+
import os
2121
import re
2222
import signal
23-
import select
2423
import sys
24+
import threading
2525
import traceback
2626

2727
if sys.version_info < (3,):
@@ -62,13 +62,11 @@ def __init__(self, library, host='127.0.0.1', port=8270, port_file=None,
6262
:param allow_stop: Allow/disallow stopping the server using
6363
``Stop Remote Server`` keyword.
6464
"""
65-
self._server = StoppableXMLRPCServer(host, int(port))
65+
self._server = StoppableXMLRPCServer(host, int(port), port_file)
6666
self._library = RemoteLibraryFactory(library)
6767
self._allow_stop = allow_stop
6868
self._register_functions(self._server)
69-
self._register_signal_handlers()
70-
self._announce_start(port_file)
71-
self._server.start()
69+
self._server.serve()
7270

7371
@property
7472
def server_address(self):
@@ -81,29 +79,13 @@ def _register_functions(self, server):
8179
server.register_function(self.get_keyword_documentation)
8280
server.register_function(self.stop_remote_server)
8381

84-
def _register_signal_handlers(self):
85-
def stop_with_signal(signum, frame):
86-
self._allow_stop = True
87-
self.stop_remote_server()
88-
for name in 'SIGINT', 'SIGTERM', 'SIGHUP':
89-
if hasattr(signal, name):
90-
signal.signal(getattr(signal, name), stop_with_signal)
91-
92-
def _announce_start(self, port_file=None):
93-
host, port = self.server_address
94-
self._log('Robot Framework remote server at %s:%s starting.'
95-
% (host, port))
96-
if port_file:
97-
with open(port_file, 'w') as pf:
98-
pf.write(str(port))
99-
10082
def stop_remote_server(self):
101-
prefix = 'Robot Framework remote server at %s:%s ' % self.server_address
10283
if self._allow_stop:
103-
self._log(prefix + 'stopping.')
104-
self._server.stop()
84+
self._server.stop_serve()
10585
return True
106-
self._log(prefix + 'does not allow stopping.', 'WARN')
86+
# TODO: Log to __stdout__? WARN?
87+
print('Robot Framework remote server at %s:%s does not allow stopping.'
88+
% self.server_address)
10789
return False
10890

10991
def _log(self, msg, level=None):
@@ -140,24 +122,59 @@ def get_keyword_documentation(self, name):
140122
class StoppableXMLRPCServer(SimpleXMLRPCServer):
141123
allow_reuse_address = True
142124

143-
def __init__(self, host, port):
144-
SimpleXMLRPCServer.__init__(self, (host, port), logRequests=False)
145-
self._shutdown = False
125+
def __init__(self, host, port, port_file=None):
126+
SimpleXMLRPCServer.__init__(self, (host, port), logRequests=False,
127+
bind_and_activate=False)
128+
self._port_file = port_file
129+
self._thread = None
130+
self._stop_server = threading.Event()
131+
132+
def start(self, log=False):
133+
self.server_bind()
134+
self.server_activate()
135+
self._thread = threading.Thread(target=self.serve_forever)
136+
self._thread.start()
137+
self._announce_start(log, self._port_file)
138+
139+
def _announce_start(self, log_start, port_file):
140+
# TODO: starting -> started
141+
if log_start:
142+
print('Robot Framework remote server at %s:%s starting.'
143+
% self.server_address)
144+
if port_file:
145+
with open(port_file, 'w') as pf:
146+
pf.write(str(self.server_address[1]))
147+
148+
def stop(self, log=False):
149+
self.shutdown()
150+
self.server_close()
151+
self._thread.join()
152+
self._announce_end(log, self._port_file)
153+
154+
def _announce_end(self, log_end, port_file):
155+
# TODO: stopping -> stopped
156+
if log_end:
157+
print('Robot Framework remote server at %s:%s stopping.'
158+
% self.server_address)
159+
if port_file and os.path.exists(port_file):
160+
os.remove(port_file) # TODO: Document that port file is removed
161+
162+
def serve(self, log=True):
163+
self._stop_server.clear()
164+
self._register_signal_handlers() # TODO: use as context manager!
165+
self.start(log)
166+
while not self._stop_server.is_set():
167+
self._stop_server.wait(1)
168+
self.stop(log)
146169

147-
def start(self):
148-
if hasattr(self, 'timeout'):
149-
self.timeout = 0.5
150-
elif sys.platform.startswith('java'):
151-
self.socket.settimeout(0.5)
152-
while not self._shutdown:
153-
try:
154-
self.handle_request()
155-
except (OSError, select.error) as err:
156-
if err.args[0] != errno.EINTR:
157-
raise
170+
def _register_signal_handlers(self):
171+
for name in 'SIGINT', 'SIGTERM', 'SIGHUP':
172+
if hasattr(signal, name):
173+
signal.signal(getattr(signal, name),
174+
lambda signum, frame: self.stop_serve())
158175

159-
def stop(self):
160-
self._shutdown = True
176+
def stop_serve(self):
177+
self._stop_server.set()
161178

162179

163180
def RemoteLibraryFactory(library):

0 commit comments

Comments
 (0)