Page 1 of 1

[SOLVED] Using Python's threading with SP

Posted: Fri Feb 06, 2015 12:17 pm
by Kamiqawa
Hey,

Is there a way to use Python socket module for creating a socket-server within a SP plugin?

For example here is how it could be done in standalone Python:

Syntax: Select all

import socket
import sys
from threading import Thread

def start_server():
print('Starting server...')
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind(('localhost', 8089))
serversocket.listen(20)

while True:
connection, address = serversocket.accept()
buf = connection.recv(1024)
if len(buf) > 0:
print('Got: '+str(buf))
break

t = Thread(target=start_server, args=())
t.start()

Sample client for testing:

Syntax: Select all

import socket

clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientsocket.connect(('localhost', 8089))
clientsocket.send('TESTING')

The thread is needed for preventing the mainthread from freezing while waiting for a new connection (you can just replace serversocket.accept() with time.sleep() in that manner).
Anyways the threading module apparently doesn't play well with Source Engine (as we know from Eventscripts), but is there a possible workaround for this in SP or ES?

Thanks
- Kamiqawa

Posted: Fri Feb 06, 2015 3:38 pm
by BackRaw
Try it :) I'm not sure, but I think you can use it if it is included in the Python3 engine provided by SP. But threads haven't been so easy in EventScripts days, so I don't know if this had to do with ES or the Source Engine not really allowing it, or something.

Posted: Fri Feb 06, 2015 4:19 pm
by Kamiqawa
Thanks for the reply! :smile:

Yeah I tested it of course first. Let me repeat the problems with different approaches.

Using from mainthread
The server stops while "sleeping", as expected.

Using threading.Thread
Strange behaviour, exactly like Eventscripts. Doesn't execute properly.
Simple sample that doesn't work either.

Syntax: Select all

from threading import Thread
from time import sleep
def loop(i=0):
i += 1
print(i)
sleep(1)
if i < 10:
loop(i)
t = Thread(target=loop)
t.start()


Ticks, gamethread etc.
Apparently TickRepeats nor the ES gamethread functions create a new thread as they still freeze the server like if they were called from the mainthread.

Posted: Fri Feb 06, 2015 9:10 pm
by Kamiqawa
Okay, so I think I got it sorted out so it fits my needs. Big thanks to Tha Pwned for providing me the key to the solution.

Apparently the problem with threads is that Python part starts sleeping and needs to be waken up. This also explains the random-ish behaviour of threads in SP and ES: if the Python code has been accidentally waken up it works for a moment (a tick?) and then sleeps again.

The solution is to keep the Python awake all the time via tick listener. So for example, the above sample code can be made to function as expected by overriding the Thread class with custom class which is being waken up on every tick.

Syntax: Select all

from threading import Thread
from time import sleep
from listeners import tick_listener_manager # Added import for tick listener

# New class extending the regular Thread
class SPThread(Thread):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
tick_listener_manager.register_listener(self._tick) # Automatically wake up every tick

def _tick(self):
pass # No need to actually do anything, the function call is enough

# The old code
def loop(i=0):
i += 1
print(i)
sleep(1)
if i < 10:
loop(i)
t = SPThread(target=loop) # Changed to use the new class
t.start()


Changed the topic subject to better describe the problem and solution as sockets didn't have anything to do with this.

Thank you,
Kamiqawa

Posted: Fri Feb 06, 2015 9:40 pm
by Doldol
FYI That is not a perfect solution, as your thread will continue to run even when you unload your plugin.
You'll have to code some signalling in to stop the Thread when the plugin is unloaded.

Edit:
What about something like this (subclassing ftw!):

Syntax: Select all

import time, threading

from core import AutoUnload
from hooks.exceptions import except_hooks
from listeners import tick_listener_manager




class StoppableSPThread(threading.Thread, AutoUnload):
def __init__(self, accuracy=1, *args, **kwargs):
super().__init__(*args, **kwargs)
self.accuracy = accuracy
tick_listener_manager.register_listener(self._tick) # Automatically wake up every tick
self._stop = threading.Event()


def run(self):
while not self.stopped:
try:
self.do()
except Exception:
except_hooks.print_exception()
time.sleep(self.accuracy)


def do(self):
raise NotImplementedError("Some exception to indicate that this should be overridden")


def _tick(self):
pass


def stop(self):
self._stop.set()
tick_listener_manager.unregister_listener(self._tick)


@property
def stopped(self):
return self._stop.is_set()


_unload_instance = stop




#Example:


class Yay(StoppableSPThread):
def __init__(self, text, *args, **kwargs):
super().__init__(*args, **kwargs)
self.text = text


def do(self):
print(self.text)


t = Yay("A Test!")
t.start()

Posted: Fri Feb 06, 2015 10:52 pm
by Kamiqawa
Good point. Unloading may not be the most likely case for me but in general a way to stop the tick listener is a good idea. Thanks, I'lll be using that as the new core then. :)