Async / long running background task

689 views
Skip to first unread message

Kevin Keller

unread,
Feb 17, 2021, 6:44:27 AM2/17/21
to py4web
I found this on Stackoverflow:


I was wondering if anyone has already done something like this in py4web?

Appreciate your help


cloudHQPowered by
cloudHQ

Alexander Beskopilny

unread,
Feb 17, 2021, 7:33:28 AM2/17/21
to py4web
for long running tasks I try to use tornado
some prototype 

def tornadoSioWsServer():

    # py4web.py run -s tornadoSioWsServer apps

    import tornado.websocket
    import time, urllib
    from tornado.httputil import url_concat
    import tornado.httpclient
    import asyncio
    import aioredis

    ws_debug = True
    connections = []

    async def consumer(channel):
        while await channel.wait_message():
            msg = await channel.get(encoding='utf-8')
            for connection in connections:
                await connection.write_message(msg)

    async def setup():
        connection = await aioredis.create_redis('redis://localhost')
        channel = await connection.subscribe('notifications')
        asyncio.ensure_future(consumer(channel))
    class web_socket_handler(tornado.websocket.WebSocketHandler):
        # This class handles the websocket channel
        @classmethod
        def route_urls(cls):
            return (r'/',cls, {})

        def simple_init(self):
            self.last = time.time()
            self.stop = False

        def open(self):
            #    client opens a connection
            self.simple_init()
            ws_debug and print(f"ws: {time.time() - self.last:.1f}: New client connected")
            self.write_message(f"ws: {time.time() - self.last:.1f}: You are connected")
            connections.append(self)

        def on_message(self, message):
            #    Message received on the handler
            ws_debug and print(f"ws: {time.time() - self.last:.1f}: received message {message}")
            self.write_message(f"ws: {time.time() - self.last:.1f}: You said - {message}")
            self.last = time.time()

        def on_close(self):
            #    Channel is closed
            ws_debug and print(f"ws: {time.time() - self.last:.1f}: connection is closed")
            self.stop= True
            connections.remove(self)

        def check_origin(self, origin):
            return True
    # socketio    pip install python-socketio


    import socketio
    sio_debug = False
    sio = socketio.AsyncServer(async_mode='tornado')

    def handle_request( response):
        pass

    @sio.event
    async def connect(sid, environ):
        sio_debug and print('sio: connect ', sid)

    @sio.event
    async def disconnect(sid):
         sio_debug and print('sio: disconnect ', sid)

    @sio.on('to_py4web')
    async def echo(sid, data):
         sio_debug and  print('sio: from client: ', data)

         http_client = tornado.httpclient.AsyncHTTPClient()

         http_client.fetch(request, handle_request)
         await sio.emit("py4web_echo", data)
    class TornadoSioWsServer(ServerAdapter):

        def run(self, handler): # pragma: no cover
            import tornado.wsgi, tornado.httpserver,  tornado.web,  tornado.ioloop
            container = tornado.wsgi.WSGIContainer(handler)
            app= tornado.web.Application([
                    web_socket_handler.route_urls(),
                    (r"/socket.io/", socketio.get_tornado_handler(sio) ),
                    (r".*", tornado.web.FallbackHandler, dict(fallback=container) ),
                 ])
            server = tornado.httpserver.HTTPServer(app)
            server.listen(port=self.port,address=self.host)

            tornado.ioloop.IOLoop.instance().start()

    return TornadoSioWsServer

stifan kristi

unread,
Feb 17, 2021, 12:21:04 PM2/17/21
to py4web
think everything works in bottle will work in py4web
e.g. base on stackoverflow link (not tested)
from py4web import action, HTTP
from threading import Thread

@action('task', method = ['GET'] )
def f():
    task_thread = Thread(target=longRunningTask) # create a thread that will execute your longRunningTask() function
    task_thread.setDaemon(True) # setDaemon to True so it terminates when the function returns
    task_thread.start() # launch the thread
    return HTTP(202)

best regards,
stifan

Val K

unread,
Feb 17, 2021, 12:27:52 PM2/17/21
to py4web
Spawn thread per request? You have unlimited resources?

среда, 17 февраля 2021 г. в 20:21:04 UTC+3, stifan kristi:

Kevin Keller

unread,
Feb 18, 2021, 3:48:41 AM2/18/21
to Val K, py4web
Thanks guys, I ll try with threading and tornado. 


cloudHQPowered by
cloudHQ
--
You received this message because you are subscribed to the Google Groups "py4web" group.
To unsubscribe from this group and stop receiving emails from it, send an email to py4web+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/py4web/57a2e4a2-60de-4d3b-8745-d52a02fdcff1n%40googlegroups.com.

Massimo

unread,
Feb 20, 2021, 12:53:55 PM2/20/21
to py4web
I expect a possible problem here. pydal done some weird stuff with threads. It basically makes sure that every thread has one database connection/session/cursor. In that when different threads serve different clients, database connections do not get mixed up. If one action creates an additional thread I do not know what db will be pointing to. Also the logic that wraps a db to commit/rollback will break because the thread may continue after the action returns.

So bottom line do not share a db object between threads you create. If you do, maybe try insider the thread body

with db.single_transaction():
     # use db only inside here

Alexander Beskopilny

unread,
Feb 3, 2022, 2:57:01 PM2/3/22
to py4web
interesting pattern for long running background  tasks
https://matthewminer.com/2015/02/21/pattern-for-async-task-queue-results

here is the py4web version with pydal
https://github.com/ali96343/lvsio
Reply all
Reply to author
Forward
0 new messages