Shutdown RPyC server from client - python-3.x

I've created a RPyC server. Connecting works, all my exposed methods work. Now I am looking to shut down the server from the client. Is this even possible? Security is not a concern as I am not worried about a rogue connection shutting down the server.
It is started with (Which is blocking):
from rpyc import ThreadPoolServer
from service import MyService
t = ThreadPoolServer(MyService(), port=56565)
t.start()
Now I just need to shut it down. I haven't found any documentation on stopping the server.

You can add to your Service class the method:
def exposed_stop(self):
pid = os.getpid()
if platform.system() == 'Windows':
PROCESS_TERMINATE = 1
handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid)
ctypes.windll.kernel32.TerminateProcess(handle, -1)
ctypes.windll.kernel32.CloseHandle(handle)
else:
os.kill(pid, signal.SIGTERM)
This will make the service get its own PID and send SIGTERM to itself. There may be an better way of doing this hiding in some dark corner of the API, but I've found no better method.
If you want to do clean-up before your thread terminates, you can set up exit traps:
t = rpyc.utils.server.ThreadedServer(service, port=port, auto_register=True)
# Set up exit traps for graceful exit.
signal.signal(signal.SIGINT, lambda signum, frame: t.close())
signal.signal(signal.SIGTERM, lambda signum, frame: t.close())
t.start() # blocks thread
# SIGTERM or SIGINT was received and t.close() was called
print('Closing service.')
t = None
shutil.rmtree(tempdir)
# etc.

In case anybody is interested, I have found another way of doing it.
I'm just making the server object on a global scope, and then adding an exposed method to close it.
import rpyc
from rpyc.utils.server import ThreadedServer
class MyService(rpyc.Service):
def exposed_stop(self):
server.close()
def exposed_echo(self, text):
print(text)
server = ThreadedServer(MyService, port = 18812)
if __name__ == "__main__":
print("server start")
server.start()
print("Server closed")
On the client side, you will have an EOF error due to the connection being remotely closed. So it's better to catch it.
import rpyc
c = rpyc.connect("localhost", 18812)
c.root.echo("hello")
try :
c.root.stop()
except EOFError as e:
print("Server was closed")
EDIT: I needed to be able to dinamically specify the server. So I came with this (Is it better ? I don't know, but it works well. Be careful though, if you have multiple server running this service: things could become weird):
import rpyc
from rpyc.utils.server import ThreadedServer
class MyService(rpyc.Service):
_server:ThreadedServer
#staticmethod
def set_server(inst=ThreadedServer):
MyService._server = inst
def exposed_stop(self):
if self._server:
self._server.close()
def exposed_echo(self, text):
print(text)
if __name__ == "__main__":
server = ThreadedServer(MyService, port = 18812)
MyService.set_server(server)
print("server start")
server.start()
print("Server closed")
PS: It probably is possible to avoid the EOF error by using Asynchronous Operations

Related

How to safely terminate a thread in Python

I have below code, where I am using OpenCV to start webcam video. Along with that I also have a thread running that pings www.google.com to check network connectivity.
import time
import cv2
import os
from threading import Thread
stopThread = False
def CheckNetwork():
global stopThread
while True:
time.sleep(60)
host = "www.google.com"
response = os.system("ping " + host)
if response == 0:
print("Internet host reachable")
else:
print("Internet host not reachable")
if stopThread:
break
def main():
global stopThread
Thread(target=CheckNetwork).start()
cam = cv2.VideoCapture(0)
while True:
ret_val, img = cam.read()
cv2.imshow('Camera', img)
key = cv2.waitKey(1)
if key == ord('q'):
stopThread = True
break
cv2.destroyAllWindows()
main()
This code is running fine. If I have to close the application by pressing q, OpenCV window closes but application keeps running for 60sec because of the thread and only after 60sec whole application terminates safely.
I wanted to know if this is a good way to close the threads. Is there any better way available which can immediately terminate threads in Python?
There's no native way of stopping a thread in Python. Instead of using a stop flag, you can also use ctypes that calls the Python API to raise an exception in the thread.
import ctypes
# Other imports...
class ThreadWithException(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
# code here...
def get_id(self):
# returns id of the respective thread
if hasattr(self, '_thread_id'):
return self._thread_id
for id, thread in threading._active.items():
if thread is self:
return id
def raise_exception(self):
thread_id = self.get_id()
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id,
ctypes.py_object(SystemExit))
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
print('Exception raise failure')

Handling a lot of concurrent connections in Python 3 asyncio

Iam trying to improve the performance of my application. It is a Python3.6 asyncio.Protocol based TCP server (SSL wrapped) handling a lot of requests.
It works fine and the performance is acceptable when only one connection is active, but as soon as another connection is opened, the client part of the application slows down. This is really noticeable once there are 10-15 client connection.
Is there a way to properly handle requests in parallel or should I resort to running multiple server instances?
/edit Added code
main.py
if __name__ == '__main__':
import package.server
server = package.server.TCPServer()
server.join()
package.server
import multiprocessing, asyncio, uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
from package.connection import Connection
class TCPServer(multiprocessing.Process):
name = 'tcpserver'
def __init__(self, discord_queue=None):
multiprocessing.Process.__init__(self)
self.daemon = True
# some setup in here
self.start()
def run(self):
loop = uvloop.new_event_loop()
self.loop = loop
# db setup, etc
server = loop.create_server(Connection, HOST, PORT, ssl=SSL_CONTEXT)
loop.run_until_complete(server)
loop.run_forever()
package.connection
import asyncio, hashlib, os
from time import sleep, time as timestamp
class Connection(asyncio.Protocol):
connections = {}
def setup(self, peer):
self.peer = peer
self.ip, self.port = self.peer[0], self.peer[1]
self.buffer = []
#property
def connection_id(self):
if not hasattr(self, '_connection_id'):
self._connection_id = hashlib.md5('{}{}{}'.format(self.ip, self.port, timestamp()).encode('utf-8')).hexdigest()
return self._connection_id
def connection_lost(self, exception):
del Connection.connections[self.connection_id]
def connection_made(self, transport):
self.transport = transport
self.setup(transport.get_extra_info('peername'))
Connection.connections[self.connection_id] = self
def data_received(self, data):
# processing, average server side execution time is around 30ms
sleep(0.030)
self.transport.write(os.urandom(64))
The application runs on Debian 9.9 and is started via systemd
To "benchmark" I use this script:
import os, socket
from multiprocessing import Pool
from time import time as timestamp
def foobar(i):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 60000))
while True:
ms = timestamp()*1000
s.send(os.urandom(128))
s.recv(1024*2)
print(i, timestamp()*1000-ms)
if __name__ == '__main__':
instances = 4
with Pool(instances) as p:
print(p.map(foobar, range(0, instances)))
To answer my own question here. I went with a solution that spawned multiple instances which were listening on base_port + x and I put a nginx TCP loadbalancer in front of it.
The individual TCPServer instances are still spawned as own process and communicate among themselves via a separate UDP connection and with the main process via multiprocessing.Queue.
While this does not "fix" the problem, it provides a somewhat scalable solution for my very specific problem.

BottlePy and PyWebView

I am trying to build a small app in Bottle, and thought I'd try using pywebview as a viewer. When I run the following file, I get two instances of the webview window. The first one shows the page, the second shows a spinning wheel cursor. Closing the second window is shutting down the web server, I believe, but not killing the thread.
Why are there two windows showing up?
import sys
import threading
from bottle import Bottle, ServerAdapter
import webview
class MyWSGIRefServer(ServerAdapter):
server = None
def run(self, handler):
from wsgiref.simple_server import make_server, WSGIRequestHandler
if self.quiet:
class QuietHandler(WSGIRequestHandler):
def log_request(*args, **kw): pass
self.options['handler_class'] = QuietHandler
self.server = make_server(self.host, self.port, handler, **self.options)
self.server.serve_forever()
def stop(self):
# self.server.server_close() <--- alternative but causes bad fd exception
self.server.shutdown()
app = Bottle()
listen_addr = 'localhost'
listen_port = 8080
server = MyWSGIRefServer(host='localhost', port=8080)
#app.route('/')
def hello():
return "Hello World!"
def start_server():
app.run(server=server, reloader=True)
try:
print(threading.enumerate())
serverthread = threading.Thread(target=start_server)
serverthread.daemon = True
print("starting web server")
serverthread.start()
print("starting webview")
webview.create_window('bottle test', "http://localhost:8080/")
print("webview closed. closing server")
sys.exit()
server.stop()
except Exception as ex:
print(ex)
The issue was using reloader=True when running the server. Setting this to False prevents the second window from appearing.

Delay opening an asyncio connection

Some of my django REST services have to connect to an asyncio server to get some information. So I'm working in a threaded environment.
While connecting, the open_connection() takes an unreasonable 2 seconds (almost exactly, always just a bit more).
Client code:
import asyncio
import datetime
def call():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
#asyncio.coroutine
def msg_to_mars():
print("connecting", datetime.datetime.now())
reader, writer = yield from asyncio.open_connection('localhost', 8888, loop=loop)
print("connected", datetime.datetime.now()) # time reported here will be +2 seconds
return None
res = loop.run_until_complete(msg_to_mars())
loop.close()
return res
call()
Server code:
import asyncio
#asyncio.coroutine
def handle_connection(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
pass
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
# Each client connection will create a new protocol instance
coro = asyncio.start_server(handle_connection, '0.0.0.0', 8888, loop=loop)
server = loop.run_until_complete(coro)
# Serve requests until Ctrl+C is pressed
print('MARS Device server serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
Both are basically copied from asyncio documentation samples for streamed communication, except for the additional assigning of event loop for threading.
How can I make this delay go away?
Turns out, the problem was in Windows DNS resolution.
Changing URLs from my computer name to 127.0.0.1 immediately killed the delays.

Python 3.4 Socket and Threading issues

import sys
import socket
import threading
import time
QUIT = False
class ClientThread(threading.Thread): # Class that implements the client threads in this server
def __init__(self, client_sock): # Initialize the object, save the socket that this thread will use.
threading.Thread.__init__(self)
self.client = client_sock
def run(self): # Thread's main loop. Once this function returns, the thread is finished and dies.
global QUIT # Need to declare QUIT as global, since the method can change it/
done = False
cmd = self.readline() #Read data from the socket and process it
while not done:
if 'quit' == cmd:
self.writeline('Ok, bye')
QUIT = True
done = True
elif 'bye' == cmd:
self.writeline('Ok, bye')
done = True
else:
self.writeline(self.name)
cmd = self.readline()
self.client.close() # Make sure socket is closed when we're done with it
return
def readline(self): # Helper function, read up to 1024 chars from the socket, and returns them as a string
result = self.client.recv(1024)
if None != result: # All letters in lower case and without and end of line markers
result = result.strip().lower()
return result
def writeline(self, text): # Helper function, writes the given string to the socket with and end of line marker at end
self.client.send(text.strip() + '\n')
class Server: # Server class. Opens up a socket and listens for incoming connections.
def __init__(self): # Every time a new connection arrives, new thread object is created and
self.sock = None # defers the processing of the connection to it
self.thread_list = []
def run(self): # Server main loop: Creates the server (incoming) socket, listens > creates thread to handle it
all_good = False
try_count = 0 # Attempt to open the socket
while not all_good:
if 3 < try_count: # Tried more than 3 times without success, maybe post is in use by another program
sys.exit(1)
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create the socket
port = 80
self.sock.bind(('127.0.0.1', port)) # Bind to the interface and port we want to listen on
self.sock.listen(5)
all_good = True
break
except socket.error, err:
print('Socket connection error... Waiting 10 seconds to retry.')
del self.sock
time.sleep(10)
try_count += 1
print( 'Server is listening for incoming connections.')
print('Try to connect through the command line with:')
print('telnet localhost 80')
print('and then type whatever you want.')
print()
print("typing 'bye' finishes the thread. but not the server",)
print("eg. you can quit telnet, run it again and get a different ",)
print("thread name")
print("typing 'quit' finishes the server")
try:
while not QUIT:
try:
self.sock.settimeout(0.500)
client = self.sock.accept()[0]
except socket.timeout:
time.sleep(1)
if QUIT:
print('Received quit command. Shutting down...')
break
continue
new_thread = ClientThread(client)
print('Incoming Connection. Started thread ',)
print(new_thread.getName())
self.thread_list.append(new_thread)
new_thread.start()
for thread in self.thread_list:
if not thread.isAlive():
self.thread_list.remove(thread)
thread.join()
except KeyboardInterrupt:
print('Ctrl+C pressed... Shutting Down')
except Exception as err:
print('Exception caught: %s\nClosing...' % err)
for thread in self.thread_list:
thread.join(1.0)
self.sock.close()
if "__main__" == __name__:
server = Server()
server.run()
print('Terminated')
Resolved many issues, these are the ones left, thank you guys!
1st error: socket.error, err.This specifically tells me that this no longer works in Python 3.4, but does not offer an alternative.
2nd Error: except socket.error, err: Python 3.4 does not support this syntax
3rd Error: self.readline(), I also have to assume writeline does not work also.
In this case, self.readline() is totally not working, I get an error that says AttributeError: 'ClientThread' object has no attribute 'readline'
This only happens after the thread is created. Console shows:
Incoming connection. Started thread.
Thread-1
Then flags that error.
4th Error: Cannot get 2to3 to run? Terminal says not recognised as internal command, and python console gives a big FU.
Can I get any rectification suggestions for the following errors please?
There are multiple issues that prevent your code from woring on python3
you're using python2 print statements, so your code can't possibly run on python3 where print() is a function.
the recv and send methods require/return bytes in python3, not str
the syntax for catching errors is except ExceptionClass as name
The first step in porting Python 2 code to Python 3 is to run it though the 2to3 program that comes with Python.
If you run your code through 2to3 with the -w option, it will fix a lot of your problems automagically.
> 2to3 -w --no-diffs socktest1.py
If you want to see what would be changed, but not change anything;
> 2to3 socktest1.py |less

Resources