[SOLVED] Using Python's threading with SP

Please post any questions about developing your plugin here. Please use the search function before posting!
Kamiqawa
Junior Member
Posts: 6
Joined: Sat Sep 01, 2012 3:23 pm

[SOLVED] Using Python's threading with SP

Postby Kamiqawa » Fri Feb 06, 2015 12:17 pm

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
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Fri Feb 06, 2015 3:38 pm

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.
My Github repositories:

Source.Python: https://github.com/backraw
Kamiqawa
Junior Member
Posts: 6
Joined: Sat Sep 01, 2012 3:23 pm

Postby Kamiqawa » Fri Feb 06, 2015 4:19 pm

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.
Kamiqawa
Junior Member
Posts: 6
Joined: Sat Sep 01, 2012 3:23 pm

Postby Kamiqawa » Fri Feb 06, 2015 9:10 pm

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
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Fri Feb 06, 2015 9:40 pm

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()
Kamiqawa
Junior Member
Posts: 6
Joined: Sat Sep 01, 2012 3:23 pm

Postby Kamiqawa » Fri Feb 06, 2015 10:52 pm

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. :)

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 66 guests