On an issue between consumer and worker

My question is a follow-up to

https://forum.djangoproject.com/t/using-background-tasks-in-django-but-how-exactly/28019/5 https://forum.djangoproject.com/t/using-background-tasks-in-django-but-how-exactly/28019

and, in particular, the part where the questioner attempts to have a consumer send a message to a worker, there called NodeManagerConsumer, in the django notes

https://channels.readthedocs.io/en/latest/topics/worker.html#worker-and-background-tasks

it is the PrintConsumer.

I am working on a game that will run on Django and will have a log-in page, a lobby for chatting and challenging other players which will lead to a private game room where the game will take place. As I am a novice programmer, this project is entirely for learning processes only. So please go gentle on me.

Based on my readings, I believe a lot of the processes of the actual game are best run using workers so I decided to practice making a simple ‘PrintConsumer’ worker to a consumer called PlayerConsumer. On its own (without the PrintConsumer), the PlayerConsumer seems to run fine and does everything else I ask it.

This is a work in progress: I thought I could start the question and save it to work on later but the editor did not have a save option. I know I need to add more but will continue it tomorrow. Sorry if you read through what I have so far.

This is precisely the architecture that I’m using for my game engine.

I have an AsyncJsonWebsocketConsumer that handles all the interactions between the browser and the server.

Each Game Title in my system is run by a worker process for that game. (With 5 active games, that’s 5 separate worker processes.)

Each consumer is a SyncConsumer.

Architecturally, the “Lobby” is just another “Game”.

This all works extremely well. It’s been in active use for about 4 years, and I’m working on it consistently now.

Sending a message between consumers (a worker process is just another type of consumer) is a matter of using the send method, with the routing information of the target consumer being the specified destination for the message.

See the docs for Sending - it really is that easy.

Feel free to ask questions here! We’re here to help.

I read through some of your other posts on consumer/workers and that was what lead me to the idea of using them but even working with consumers and websockets is new to me so it has taken quite awhile.

As I mentioned in the closed question, I followed what the two of you were discussing. I tried that trick where one can separate the consumer from the websocket and sent the message in the django shell, nothing happened in the terminal running the worker which (as you mentioned) indicated there was a problem with the connection.

If you want assistance with that, you’re going to need to post the code for your consumer, your routing configuration, and the commands you’re issuing in the shell to send the message, along with the message you’re trying to send.

If you’re getting any error messages in either console, please post them.

It would also be helpful to see the command you’re using to run the worker and the shell, along with your INSTALLED_APPS and CHANNEL_LAYERS settings.

(Please remember to post your code between lines of three backticks - ` for each file, and to identify the name of the file containing the code you’re posting.)

To keep things simple, I will post (what I believe to be) the important material. If there is anything else needed, I will add it. This is the lobby/consumers.py:

class PlayerConsumer(WebsocketConsumer):
	def __init__(self,*args,**kwargs):
		super().__init__(*args, **kwargs)
		self.user = None
		self.user_inbox = None
		self.room_name="public"
		
		if self.groups is None:
			self.groups = []
		
		self.room_group_name = f"chat_{self.room_name}"
		
	def connect(self):
		self.user = self.scope['user']
		print(self.user)

		#self.room_group_name = f"chat_{self.room_name}"

		# Join room group
		async_to_sync(self.channel_layer.group_add)(
			self.room_group_name, self.channel_name
		)
		# Join private group
		async_to_sync(self.channel_layer.group_add)(
			f"user_{self.user.username}",self.channel_name
		)

		self.accept()
		
	def disconnect(self,close_code):
		pass
		"""
		if self.MyUser.is_authenticated:
			async_to_sync(self.channel_layer.group_discard)(
				self.user_inbox,
				self.channel_name,
			)
		"""
	def receive(self,text_data):
		text_data_json = json.loads(text_data)
		message = text_data_json["message"]
		
		#=None,bytes_data=None
		if message.startswith('win'):
			print(message)
			async_to_sync(self.channel_layer.send)(
				'print-consumer',{
					'type':'test',
					'text':message,
				}
			)
		elif message.startswith('/pm'):
			split = message.split(' ',2)
			target = split[1]
			target_msg = split[2]
			async_to_sync(self.channel_layer.group_send)(
				f'user_{target}',
				{'type':'private.message',
				'sender': self.user.username,
				'message':target_msg,
				}
			)
			self.send(json.dumps({
				'type':'private message delivered',
				'target': target,
				'message': target_msg,
			}))
		else:
			async_to_sync(self.channel_layer.group_send)(
			self.room_group_name,{"type":"chat.message",
			'sender': self.user.username,
			"message": message}
			)
				
		return

	def chat_message(self,event):
		message = f"(public message from "+event['sender']+') '+event["message"]
		self.send(json.dumps({'message':message}))
		
	def private_message(self,event):
		message = event['message']
		message ="(private message from "+event['sender']+') '+message
		self.send(json.dumps({'message':message}))
		

	def start_game(self,event):
		pass

	def join_game(self):
		pass

class PrintConsumer(SyncConsumer):
	print("Worker here!")
	def test(self,message):
		print("Test test!")
		print(f"I send you the message "+message['text'],flush=True)
 

I will post follow-up code, but it might be easier if I do it in separate comments.

I must go so I will continue this tomorrow.

From roshamo/asgi

import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter,ChannelNameRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from lobby.routing import lobby_websocket_urlpatterns
from game.routing import game_websocket_urlpatterns
```
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘roshambo.settings’)

django_asgi_app = get_asgi_application()
from game.consumers import GameConsumer, GameManager
from lobby.consumers import PlayerConsumer,PrintConsumer
application = ProtocolTypeRouter({
“http”: django_asgi_app,
“websocket”: AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(lobby_websocket_urlpatterns +
game_websocket_urlpatterns))
),
“channel”: ChannelNameRouter({“print-consumer”:PrintConsumer.as_asgi()})
})

roshamo/settings

DEBUG = True
ALLOWED_HOSTS = ['127.0.0.1']
# Application definition
INSTALLED_APPS = [
    'accounts',
    'daphne',
    'channels',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'chat',
    'game',
    'lobby',
]
MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',  
    'django.middleware.security.SecurityMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'roshambo.urls'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
WSGI_APPLICATION = 'roshambo.wsgi.application'
ASGI_APPLICATION = "roshambo.asgi.application"
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG":
        {"hosts":[("127.0.0.1",6379)],
        },
    },
}
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
#AUTH_USER_MODEL = "accounts.MyUser"
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_REDIRECT_URL = '/lobby/'

lobby/routing.py

from django.urls import re_path
from . import consumer
lobby_websocket_urlpatterns = [
	re_path(r"ws/lobby/public/", consumers.PlayerConsumer.as_asgi())]

The folder structure:

roshambo
   lobby
      consumers.py
      routing.py
   roshambo
      asgi.py
      settings.py
   manage.py

Ithink this is all.

Close. I still need to see the commands you are issuing to run this - both the worker and your shell, along with the specific commands you are running in the shell to try it and the console logs.

To start the redis server:

$ docker run --rm -p 6379:6379 redis:7

to run the worker:

$ uv run manage.py runworker PrintConsume

In the django shell:
>>from channels.layers import get_channel_layer
>>from asgiref.sync import async_to_sync
>>channel_layer = get_channel_layer()
>>async_to_sync(channel_layer.send)('print-consumer',{'type':'test','message':'A message'})

As mentioned, the runworker command runs fine I just get no result in the terminal

Please, copy/paste and post the actual output from your runworker and django shell sessions. Do not extract or summarize what’s being displayed.

This is all the worker terminal shows (lines 3-4 are deugging lines)

$ uv run manage.py runworker PrintConsumer
Running worker for channels ['PrintConsumer']
Worker here!
getting here is half the job

I posted exactly what happens in the the django shell. Nothing happens afterwards, just moves to a new line. No errors anywhere.

In another terminal, I tried to runserver. Nothing there either:

$uv run manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 16, 2026 - 17:00:26
Django version 5.2.8, using settings 'roshambo.settings'
Starting ASGI/Daphne version 4.2.1 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Worker here!
getting here is half the job


Then nothing. Just normal crickets

I can log multiple players in. This is the result in server terminal:

$ uv run manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 16, 2026 - 17:00:26
Django version 5.2.8, using settings 'roshambo.settings'
Starting ASGI/Daphne version 4.2.1 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Worker here!
getting here is half the job
HTTP GET /accounts/login/ 200 [2.83, 127.0.0.1:51062]
Not Found: /favicon.ico
HTTP GET /favicon.ico 404 [1.45, 127.0.0.1:51062]
HTTP POST /accounts/login/ 200 [5.42, 127.0.0.1:51062]
HTTP POST /accounts/login/ 200 [5.53, 127.0.0.1:51062]
HTTP POST /accounts/login/ 200 [7.13, 127.0.0.1:51062]
HTTP GET /accounts/login/ 200 [0.09, 127.0.0.1:51062]
HTTP POST /accounts/login/ 200 [6.13, 127.0.0.1:51062]
HTTP GET /accounts/login/ 200 [0.70, 127.0.0.1:51095]
Not Found: /favicon.ico
HTTP GET /favicon.ico 404 [0.29, 127.0.0.1:51095]
HTTP POST /accounts/login/ 200 [6.38, 127.0.0.1:51095]
HTTP POST /accounts/login/ 200 [6.74, 127.0.0.1:51159]
HTTP POST /accounts/login/ 200 [8.80, 127.0.0.1:51159]
HTTP GET /accounts/login/ 200 [0.08, 127.0.0.1:51662]
HTTP GET /accounts/login/ 200 [0.15, 127.0.0.1:51832]
Not Found: /favicon.ico
HTTP GET /favicon.ico 404 [0.26, 127.0.0.1:51832]
HTTP POST /accounts/login/ 302 [12.02, 127.0.0.1:51832]
3
frightfullyFrigid
<QuerySet [<User: jaden>, <User: daisyDuck>, <User: frightfullyFrigid>, <User: advil>, <User: brianketelboeter>]>
HTTP GET /lobby/ 200 [11.47, 127.0.0.1:51832]
HTTP GET /static/lobby/lobby.js 304 [0.07, 127.0.0.1:51832]
WebSocket HANDSHAKING /ws/lobby/public/ [127.0.0.1:51844]
frightfullyFrigid
WebSocket CONNECT /ws/lobby/public/ [127.0.0.1:51844]
HTTP POST /accounts/login/ 302 [10.75, 127.0.0.1:51855]
1
jaden
<QuerySet [<User: jaden>, <User: daisyDuck>, <User: frightfullyFrigid>, <User: advil>]>
HTTP GET /lobby/ 200 [0.65, 127.0.0.1:51855]
HTTP GET /static/lobby/lobby.js 304 [0.01, 127.0.0.1:51855]
WebSocket HANDSHAKING /ws/lobby/public/ [127.0.0.1:51858]
jaden
WebSocket CONNECT /ws/lobby/public/ [127.0.0.1:51858]

I had some issues logging in a couple times.

Something that might show progress, an error message in the worker terminal (might have closed my redis terminal causing this error). Sorry.

Traceback (most recent call last):
File “C:\Users\Brian Ketelboeter\Roshambo\roshambo\manage.py”, line 22, in
main()
File “C:\Users\Brian Ketelboeter\Roshambo\roshambo\manage.py”, line 18, in main
execute_from_command_line(sys.argv)
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\django\core\management_init_.py”, line 442, in execute_from_command_line
utility.execute()
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\django\core\management_init_.py”, line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\django\core\management\base.py”, line 416, in run_from_argv
self.execute(*args, **cmd_options)
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\django\core\management\base.py”, line 460, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\channels\management\commands\runworker.py”, line 45, in handle
worker.run()
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\asgiref\server.py”, line 61, in run
event_loop.run_until_complete(self.arun())
File “C:\Users\Brian Ketelboeter\AppData\Roaming\uv\python\cpython-3.12.11-windows-x86_64-none\Lib\asyncio\base_events.py”, line 691, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\asgiref\server.py”, line 78, in arun
await asyncio.gather(self.application_checker(), handle())
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\asgiref\server.py”, line 74, in handle
await self.handle()
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\channels\worker.py”, line 30, in handle
[listener.result() for listener in listeners]
^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\channels\worker.py”, line 37, in listener
message = await self.channel_layer.receive(channel)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\channels_redis\core.py”, line 373, in receive
return (await self.receive_single(channel))[1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\channels_redis\core.py”, line 397, in receive_single content = await self._brpop_with_clean(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\channels_redis\core.py”, line 233, in _brpop_with_clean
result = await connection.bzpopmin(channel, timeout=timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis\asyncio\client.py”, line 725, in execute_command
return await conn.retry.call_with_retry(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis\asyncio\retry.py”, line 55, in call_with_retry raise error
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis\asyncio\retry.py”, line 50, in call_with_retry return await do()
^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis\asyncio\client.py”, line 700, in _send_command_parse_response
return await self.parse_response(conn, command_name, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis\asyncio\client.py”, line 746, in parse_response
response = await connection.read_response()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis\asyncio\connection.py”, line 607, in read_response
response = await self._parser.read_response(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis_parsers\resp2.py”, line 82, in read_response
response = await self._read_response(disable_decoding=disable_decoding)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis_parsers\resp2.py”, line 90, in _read_response raw = await self._readline()
^^^^^^^^^^^^^^^^^^^^^^
File “C:\Users\Brian Ketelboeter\Roshambo.venv\Lib\site-packages\redis_parsers\base.py”, line 470, in _readline
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
redis.exceptions.ConnectionError: Connection closed by server.

What version of Python are you using? (It looks like you might be using 3.12?)

What version of channels_redis?

You might want to try this with Python 3.13 and Redis 6.

Where is this line coming from? I don’t see that in any of the code you’ve posted.

This line came from the game app (the next part of the project connected to the game play). The above consumers are both in the lobby part.