Celery task not waiting for database updates - django

Can someone explain why I can't get the updated database records in the Celery task_success signal?
#consumers.py
#database_sync_to_async
def save_to_db(game)
Result.objects.create(game=game)
class GameConsumer(AsyncWebsocketConsumer):
...
async def save_result(self, event):
await save_to_db(self.game)
#tasks.py
#shared_task(name="run_game")
def run_game():
...
async_to_sync(channel_layer.group_send)(
'game',
{
'type': 'save.result'
}
)
return(game)
#task_success.connect
def task_success_handler(sender=run_game, result=None, **kwargs):
game_results = Game.objects.get(id=result.id).results.all()
async_to_sync(channel_layer.group_send)(
'game',
{
'type': 'end.game',
'data': game_results
}

Related

500 error on django channels send message to group

I have the following Django channels consumer but when I try to send data to the stocks group،
it returns a 500 error.
Also, I don't get any error logs.
Here is the consumer:
class AuthRequiredConsumer(JsonWebsocketConsumer):
def connect(self):
user = self.scope['user']
print(isinstance(user, AnonymousUser))
if isinstance(user, AnonymousUser):
raise DenyConnection("authentication required")
self.accept()
class ChatConsumer(AuthRequiredConsumer):
groups = ['stocks']
channel_layer_alias = "stocks"
def stocks_data(self, event):
print(event)
return self.send_json({'message': event['text']})
def receive_json(self, content, **kwargs):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"stocks",
{
"type": "stocks.data",
"text": "Hello there!",
},
)
message = content["message"]
self.send_json({"message": message})
Also, I use this middle to authenticate users by the token:
#database_sync_to_async
def get_user(token_key):
try:
token = Token.objects.get(key=token_key)
return token. user
except Token.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
super().__init__(inner)
async def __call__(self, scope, receive, send):
try:
token_key = (dict((x.split('=') for x in scope['query_string'].decode().split("&")))).get('token', None)
except ValueError:
print("error")
token_key = None
scope['user'] = AnonymousUser() if token_key is None else await get_user(token_key)
return await super().__call__(scope, receive, send)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
And here is the routing config:
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": TokenAuthMiddlewareStack(
URLRouter(
[
re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi())
]
)
)
,
}
)
I debugged the receive_json method using breakpoints And I saw self.channel_name and self.channel_layer is None!!
Does anyone have an idea what's up with this consumer?
Probably you forgot to set the CHANNEL_LAYERS property at settings file.
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}

Django Channels: Event loop is closing when using thread

I want to show a countdown and then later start a game loop. The code is getting excuted and the messages are send but i always get a RuntimeError. I would be interested in a fix or a maybe better solution that i can apply. I was also thinking about splitting things into two Consumers but i dont know how this would fix this. Thanks in advance.
This error message is popping up multiple times.
Task exception was never retrieved
future: <Task finished name='Task-60' coro=<Connection.disconnect() done, defined at D:\Programming\Fullstack\geogame\geogame_backend\env\lib\site-packages\redis\asyncio\connection.py:819> exception=RuntimeError('Event loop is closed')>
Traceback (most recent call last):
File "D:\Programming\Fullstack\geogame\geogame_backend\env\lib\site-packages\redis\asyncio\connection.py", line 828, in disconnect
self._writer.close() # type: ignore[union-attr]
File "C:\Program Files\Python310\lib\asyncio\streams.py", line 337, in close
return self._transport.close()
File "C:\Program Files\Python310\lib\asyncio\selector_events.py", line 698, in close
self._loop.call_soon(self._call_connection_lost, None)
File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 753, in call_soon
self._check_closed()
File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 515, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
This is my consumer
class LobbyConsumer(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(args, kwargs)
self.players = None
self.game_finished = None
self.host = None
self.user = None
self.lobby_code = None
self.lobby_group_code = None
self.lobby = None
def connect(self):
self.lobby_code = self.scope['url_route']['kwargs']['lobby_code']
if Lobby.objects.filter(code=self.lobby_code).exists():
self.lobby = Lobby.objects.get(code=self.lobby_code)
else:
print("DOESNT EXIST")
self.accept()
self.send(json.dumps({"type": "error", "message": "no_match"}))
self.close()
return
self.lobby_group_code = f"lobby_{self.lobby_code}"
self.host = Lobby.objects.select_related('host').get(code=self.lobby_code).host
self.user = self.scope['user']
if self.user.is_authenticated:
self.accept()
print(True)
else:
self.close(code=4004)
self.lobby.users.add(self.user)
self.lobby.save()
players_queryset = self.lobby.users.all()
self.players = []
for player in players_queryset:
self.players.append({
"username": player.username,
"email": player.email
})
async_to_sync(self.channel_layer.group_add)(
self.lobby_group_code,
self.channel_name,
)
async_to_sync(self.channel_layer.group_send)(
self.lobby_group_code,
{
'type': 'status',
'message': "Welcome",
'has_started': self.lobby.has_started,
'host': self.host.username,
'players': self.players,
'lobby_code': self.lobby_code,
'max_players': self.lobby.max_players,
}
)
def start_game(self):
countdown = 4
for i in range(countdown):
async_to_sync(self.channel_layer.group_send)(
self.lobby_group_code,
{
'type': 'status',
'has_started': self.lobby.has_started,
'host': self.host.username,
'players': self.players,
'lobby_code': self.lobby_code,
'countdown': countdown - 1
}
)
sleep(1)
countdown -= 1
def receive(self, text_data=None, bytes_data=None):
command = ""
text_data_json = json.loads(text_data)
print(text_data_json)
if "command" in text_data_json:
command = text_data_json['command']
if command == "start":
self.lobby.has_started = True
print("start")
self.lobby.save()
async_to_sync(self.channel_layer.group_send)(
self.lobby_group_code,
{
'type': 'status',
'message': "Welcome",
'has_started': self.lobby.has_started,
'host': self.host.username,
'players': self.players,
'lobby_code': self.lobby_code,
'countdown': 3
}
)
thread = Thread(target=self.start_game, args=())
thread.start()
def disconnect(self, close_code):
print(f"Connection Cancelled")
async_to_sync(
self.channel_layer.group_discard(
self.lobby_group_code,
self.channel_name,
))
self.close()
def chat_message(self, event):
self.send(text_data=json.dumps(event))
def status(self, event):
self.send(text_data=json.dumps(event))
I had the same problem
The problem is in channels_redis
Using RedisPubSubChannelLayer solves this problem.
However, be careful ahead RedisPubSubChannelLayer is in beta
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer",
"CONFIG": {
"hosts": ["redis://***:***#***.com:6379"],
},
},
}
I had a problem with event loop closing after an arbitrary amount of time. The issue was in the channels-redis module. It got updated accidentally. After checking every module's version, I rolled it back and it worked well again.

While testing my Websocket Consumer a model object is not created (Django Channels)

I'm new in Django Channels and I'm trying to build a simple chat app. But when I'm trying to test my async Websocket Consumer I run into the following exception: chat.models.RoomModel.DoesNotExist: RoomModel matching query does not exist.
It seems like the test room is not created.
test.py file is the following:
class ChatTest(TestCase):
#sync_to_async
def set_data(self):
room = RoomModel.objects.create_room('Room1', 'room_password_123')
room_slug = room.slug
user = User.objects.create_user(username='User1', password='user_password_123')
print(RoomModel.objects.all()) # querySet here contains the created room
return room_slug, user
async def test_my_consumer(self):
room_slug, user = await self.set_data()
application = URLRouter([
re_path(r'ws/chat/(?P<room_name>\w+)/$', ChatConsumer.as_asgi()),
])
communicator = WebsocketCommunicator(application, f'/ws/chat/{room_slug}/')
communicator.scope['user'] = user
connected, subprotocol = await communicator.connect()
self.assertTrue(connected)
await communicator.send_json_to({'message': 'hello'})
response = await communicator.receive_json_from()
self.assertEqual('hello', response)
await communicator.disconnect()
My consumer.py file is the following:
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f'chat_{self.room_name}'
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
async def receive(self, text_data):
self.message = json.loads(text_data).get('message')
data = {'type': 'chat_message'}
data.update(await self.create_message())
await self.channel_layer.group_send(self.room_group_name, data)
async def chat_message(self, event):
await self.send(text_data=json.dumps({
'message': event['message'],
'user': event['user'],
'timestamp': event['timestamp']
}))
#database_sync_to_async
def create_message(self):
print(RoomModel.objects.all()) # qyerySet turns out to be empty here
room = RoomModel.objects.get(slug=self.room_name)
msg = room.messagemodel_set.create(text=self.message, user_name=self.scope['user'])
return {
'message': msg.text,
'user': self.scope['user'].username,
'timestamp': msg.timestamp.strftime("%d/%m/%Y, %H:%M")
}
I would be grateful for any help.
When you try to run room = RoomModel.objects.create_room('Room1', 'room_password_123') you are trying to commit a transaction.
And this cannot happen until the end of the test because TestCase wraps each test within a transaction. It waits until the end of the test to create this object.
And since you are awaiting set_data, the flow goes on to execute the rest of the test, i.e., it reaches the call to create_message where it tries to get the RoomModel object, which will be not present in the db yet since the transaction has not been committed.
I had a similar problem that I solved using this link https://github.com/django/channels/issues/1110. To summarize, you have to change TestCase to TransactionTestCase

django channels group_send not working suddenly

I have recently converted my existing django project to have a react front end. However , im facing the issue whereby the backend of my django-channels is not working. The issue im having is that the group_send method is not working in my case , as such the consumer does not receive the information generated by a signal in my models. Here is my code :
consumers.py
class NotificationConsumer (AsyncJsonWebsocketConsumer):
async def connect (self):
close_old_connections()
user = self.scope["user"]
if not user.is_anonymous:
await self.accept()
#connects the user to his/her websocket channel
group_name = "notifications_{}".format(str(user))
print(group_name)
await self.channel_layer.group_add(group_name, self.channel_name)
async def disconnect (self, code):
close_old_connections()
user = self.scope["user"]
if not user.is_anonymous:
#Notifications
notifications_group_name = "notifications_{}".format(str(user))
await self.channel_layer.group_discard(notifications_group_name, self.channel_name)
async def user_notification (self, event):
close_old_connections()
print('Notification recieved by consumer')
await self.send_json({
'event': 'notification',
'data': {
'event_type': 'notification',
'notification_pk': event['notification_pk'],
'link': event['link'],
'date_created': event['date_created'],
'object_preview': event['object_preview'],
}
})
print(event)
Models/signals
def ChannelNotification(sender, instance, created, **kwargs):
if created:
channel_layer = get_channel_layer()
print(channel_layer)
group_name = "notifications_{}".format(str(instance.target))
print('inside channel signal')
print(group_name)
async_to_sync(channel_layer.group_send)(
group_name, {
"type": "user_notification",
"notification_pk": instance.pk,
"link": instance.object_url,
"date_created": instance.time.strftime("%Y-%m-%d %H:%M:%S"),
"object_preview": instance.object_preview,
}
)
Whenever an notification object is created in the database , the signal will be sent to my ChannelNotification which receives this information and transmit it to the consumer via group send . It used to work perfectly but im not sure what happened after i converted my project to have react as a frontend.

django channels - delayed messaging does not work

I am trying to imitate a time consuming django response, by delaying group message in Django channels, but it does not work.
The consumer is very simple:
class ExportConsumer(JsonWebsocketConsumer):
...
def delayed_message(self, event):
print("In delayed")
time.sleep(5)
self.send(text_data=json.dumps({
'message': 'delayed!'
}))
def immediate_message(self, event):
print("In Immediate")
self.send(text_data=json.dumps({
'message': 'immediate_message!'
}))
and in views I just send to a group two messages - for delayed and immediate processing:
class DecisionListView(PaginatedListView):
...
def get(self, *args, **kwargs):
async_to_sync(get_channel_layer().group_send)(
'5e001793-18be-4a4b-8caf-c4a8144a11d2',
{
'type': 'immediate_message',
'message': 'message'
}
)
async_to_sync(get_channel_layer().group_send)(
'5e001793-18be-4a4b-8caf-c4a8144a11d2',
{
'type': 'delayed_message',
'message': 'message'
}
)
return super().get(*args, **kwargs)
The 'immediate message' is delivered. And I get In delayed on terminal (which means that it reaches the delayed_message but the message is never delivered (unless I comment out time.sleep.
What am I doing wrong?
UPDATE::
AsyncJsonWebsocketConsumer does not work as well:
consumers.py:
class ExportConsumer(AsyncJsonWebsocketConsumer):
...
async def delayed_message(self, event):
await asyncio.sleep(5)
await self.send(text_data=json.dumps({
'message': 'whatever'
}))
views.py:
class DecisionListView(PaginatedListView):
...
def get(self, *args, **kwargs):
_group_send = get_channel_layer().group_send
_sync_group_send = async_to_sync(_group_send)
_sync_group_send('5e001793-18be-4a4b-8caf-c4a8144a11d2',{"type":'delayed_message', 'message':'hello'})
return super().get(*args, **kwargs)
UPDATE2:
The only thing that works for me is asyncio.create_task:
class ExportConsumer(AsyncJsonWebsocketConsumer):
async def delayed_task(self):
await asyncio.sleep(5)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': 'LATER'
}
)
async def delayed_message(self, event):
asyncio.create_task(self.delayed_task())
await self.send_json('NOW')
async def chat_message(self, event):
message = event['message']
await self.send_json(message)
you should be using an AsyncJsonWebsocketConsumer and async methods then you can use the sleep method from asyncio.
time.sleep will block your entire server.
-- unrelated
you should be sending delayed.message not delayed_message (channels replaces . with _ when calling functions)