Saturday, June 23, 2018

Re: Django Channels 2.0 - How to init and persist/communicate with only one instance of background worker for every channel_layer.send?

I'm not sure why that would happen - the code that chooses what application instance to use is here: https://github.com/django/asgiref/blob/master/asgiref/server.py (under get_or_create_application_instance).

If you're always using a single channel name, which gets passed as scope_id (see here: https://github.com/django/channels/blob/master/channels/worker.py), it really should be returning the same instance. I recommend you poke around in there and do some debugging to figure out what's going on.

Andrew

On Fri, Jun 22, 2018 at 9:46 AM itsnate_b <nathan.bluvol@gmail.com> wrote:
I am only using one channel name and my expectation is that only one instance of the background worker class should be initiated/used. Instead, I am seeing a new class being initiated for each call routed to the background worker class. This is unexpected...


On Thursday, June 21, 2018 at 3:55:23 PM UTC-4, Andrew Godwin wrote:
The worker server tries to keep one application instance open per incoming channel name, but that's no guarantee. It will always make a new instance for different channel names. What kind of behaviour are you expecting?

Andrew

On Tue, Jun 19, 2018 at 6:45 AM itsnate_b <nathan...@gmail.com> wrote:
I am using Django 2.0.5, Python 3.6.2, Django Channels 2.1.1, aiohttp 3.1.3.

I have created a background worker class which is initialized on a route to the appropriate event/function. Problem is that it initializes a *new* instance of the class on every triggered call, meaning that I cannot access contextual variables (they don't persist). Is there a way to trigger the same (i.e. first) and only class repeatedly on each routed call? I can change it all to function calls with global variables stored for the relevant asyncio futures and/or booleans, but would really prefer not to do that....any suggestions?

My (simplified) code:

consumers.py

from channels.generic.websocket import AsyncWebsocketConsumer
from channels.consumer import AsyncConsumer
from channels.layers import get_channel_layer
import json
import asyncio
import aiohttp
import datetime


class mainConsumer(AsyncWebsocketConsumer):

    async def connect(self):
        await self.channel_layer.group_add('myGroup', self.channel_name)
        await self.accept()

    async def disconnect(self, closeCode):
        await self.channel_layer.group_discard('myGroup', self.channel_name)

    async def receive(self, text_data):
        eventData = json.loads(text_data)
        print(eventData)
        if eventData['toggle'] == 'on':
            await self.channel_layer.send('myToggle',{"type": "openConnection"})
        elif eventData['toggle'] == 'off':
            await self.channel_layer.send('myToggle',{"type": "closeConnection"})

    async def myUpdate(self, data):
        updateMsg = #some data processing here
        await self.send(text_data=json.dumps(updateMsg))


class backgroundConsumer(AsyncConsumer):

    def __init__(self, scope):
        self.scope = scope
        self.turnOnListener = True
        self.channelLayer = get_channel_layer()
        self.t1 = None
        print('inside init') # this will print init on every channel_layer.send to 'myToggle' in mainConsumer

    async def listen(self):
        
        async with aiohttp.ClientSession() as session:
            async with session.ws_connect('...streaming url...', ssl=False) as ws:
                await ws.send_str('...some string...')
                async for msg in ws:
                    if msg.type == aiohttp.WSMsgType.TEXT:
                        if msg.data.startswith('2'):
                            await ws.send_str('3')
                        else:
                            await self.parseListenData(msg)

    async def parseListenData(self, msgContent):
         # process and create updateMsg dict with type: 'myUpdate' (so that it gets to the myUpdate function in main Consumer)
         await self.channelLayer.group_send('myGroup', updateMsg)

    async def openConnection(self, event):
        if(self.turnOnListener): # THIS IS ALWAYS TRUE BECAUSE THE ROUTE INITIALIZES A NEW INSTANCE OF THIS CLASS!!
            await self.channelLayer.group_add(
                'myGroup',
                self.channel_name,
            )
            self.turnOnListener = False # This only changes it for the current instance (obviously)
            loop = asyncio.get_event_loop()
            self.t1 = asyncio.run_coroutine_threadsafe(self.listen(), loop=loop)
        else:
            print('already turned on listener')

    async def closeConnection(self, event):
        loop = asyncio.get_event_loop()
        loop.call_soon_threadsafe(self.t1.cancel) # t1 is always None because the route initialized a new instance of this class!!

routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
import dashboard.routing
from dashboard.consumers import backgroundConsumer

application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            dashboard.routing.websocket_urlpatterns
        )
    ),
    'channel': ChannelNameRouter({
        "myToggle": backgroundConsumer,
    }),
})


--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/431ada54-74cb-4822-9b33-5dcb0326fb9c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/541378d0-6e7d-43ea-9b08-016aefd38702%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CAFwN1ur_%3DTM64aa8D8q5wMhD1ntohNDTYVvX60P-1%3Dh9TMBrVA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment