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.
Related
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)],
},
},
}
I've been using a websockets connection with django-channels for a chatroom app with the following routing:
re_path(r'ws/chat/(?P<room_name>\w+)/participant/(?P<user>\w+)/$', consumers.ChatConsumer.as_asgi())
So if the id of my chatroom is 1, to connect to WS I would use the following url with 2 being the id of the participant that wants to enter the room:
ws/chat/1/participant/1/
Now i changed the id of my room model to UUID so now to connect to a room I need to use the following url
ws/chat/84f48468-e966-46e9-a46c-67920026d669/participant/1/
where "84f48468-e966-46e9-a46c-67920026d669" is the id of my room now, but I'm getting the following error:
raise ValueError("No route found for path %r." % path)
ValueError: No route found for path 'ws/chat/84f48468-e966-46e9-a46c-
67920026d669/participant/1/'.
WebSocket DISCONNECT /ws/chat/84f48468-e966-46e9-a46c-67920026d669/participant/1/
[127.0.0.1:50532]
My consumers:
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.user = self.scope['url_route']['kwargs']['user']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
delete = delete_participant(self.room_name, self.user)
if delete == 'not_can_delete_user':
pass
else:
# Leave room group
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f'chat_{self.room_name}',
{
'type': 'receive',
'message': "USER_DISCONNECT",
'body': {
'participant': delete,
'idParticipant': self.user
}
}
)
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
def receive(self, text_data=None, type='receive', **kwargs):
if isinstance(text_data, dict):
text_data_json = text_data
message = text_data_json['message']
body = text_data_json['body']
else:
text_data_json = json.loads(text_data)
message = text_data_json['message']
body = text_data_json['body']
self.send(text_data=json.dumps({
'message': message,
"body": body
}))
What is wrong?
try this regex:
re_path(r'ws/chat/(?P<room_name>[A-Za-z0-9_-]+)....
format the group id and remove the '-' pass it in the javascript and use groupId2 instead.
groupId2 = groupId.replaceAll("-","");
i think this is better than searching for a regex and the problem is that django uuid field is 32 bytes not 36 bytes as it would be with the '-'.
so reading the full log will show you that a value error was raised.
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
}
I use the last version of django channels(V3) and i have two consummers.
This is my routing.py
application = ProtocolTypeRouter({
"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^ws/user/(?P<user_id>\d+)/$", consumers.UserConsumer),
url(r"^ws/notification/(?P<room_name>\w+)/$", con.NotificationConsumer),
])
),
})
My first app.consummes.py
class UserConsumer(WebsocketConsumer):
user_number = 0
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['user_id']
self.room_group_name = self.room_name
print("connected", self.room_group_name)
self.user_number+= 1
print("user_number", self.user_number)
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
print("deconnected")
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
proposal_identifiant = text_data_json['proposal_identifiant']
sender = text_data_json['sender']
messages = text_data_json['messages']
owner = text_data_json['owner']
conversator = text_data_json['conversator']
last_sender = text_data_json['last_sender']
type_ad = text_data_json['type_ad']
ad_id = text_data_json['ad_id']
price = text_data_json['price']
sending_by = text_data_json['sending_by']
price_is_changed = text_data_json['price_is_changed']
accepted = text_data_json['accepted']
from_send_message = text_data_json['from_send_message']
users_id = []
users_id.append(owner)
users_id.append(conversator)
winner_or_looser = text_data_json['winner_or_looser']
try:
try:
if proposal_identifiant and get_current_proposal(proposal_identifiant):
# we create message if proposal exist
if accepted == False:
update_proposal(proposal_identifiant, last_sender, price, price_is_changed, accepted)
create_new_message(proposal_identifiant, sender, messages)
else:
if from_send_message == True:
update_proposal(proposal_identifiant, last_sender, price, price_is_changed, accepted)
create_new_message(proposal_identifiant, sender, messages)
else:
try:
create_new_delivery(
owner,
conversator,
proposal_identifiant,
type_ad,
ad_id,
price,
accepted,
)
create_new_message(proposal_identifiant, sender, messages)
winner_or_looser = True
except IntegrityError:
print("error")
return self.send(text_data=json.dumps({
'error': "IntergyError"
}))
else:
# we create at first proposal and message
# print("new_proposal")
new_proposal = create_new_proposal(
owner,
conversator,
last_sender,
type_ad,
ad_id,
price
)
# print(new_proposal.id)
proposal_identifiant = new_proposal.id
# print(proposal_identifiant)
create_new_message(proposal_identifiant=new_proposal.id, sender=sender, messages=messages)
for id in users_id:
self.room_group_name = str(id)
# Send message to room group
# print(self.room_group_name)
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'proposal_identifiant': proposal_identifiant,
'sender': sender,
'messages': messages,
'statut': True,
'read_or_not': False,
'owner': owner,
'conversator': conversator,
'last_sender': last_sender,
'type_ad': type_ad,
'ad_id': ad_id,
'price': price,
'sending_by': sending_by,
'price_is_changed': price_is_changed,
'accepted': accepted,
'from_send_message': from_send_message,
'winner_or_looser':winner_or_looser,
}
)
except:
raise
except:
raise
# Receive message from room group
def chat_message(self, event):
proposal_identifiant = event['proposal_identifiant']
sender = event['sender']
messages = event['messages']
owner = event['owner']
conversator = event['conversator']
last_sender = event['last_sender']
type_ad = event['type_ad']
ad_id = event['ad_id']
price = event['price']
sending_by = event['sending_by']
price_is_changed = event['price_is_changed']
accepted = event['accepted']
from_send_message = event['from_send_message']
winner_or_looser = event['winner_or_looser']
# Send message to WebSocket
self.send(text_data=json.dumps({
'proposal_identifiant': proposal_identifiant,
'sender': sender,
'messages': messages,
'owner': owner,
'conversator': conversator,
'last_sender': last_sender,
'type_ad': type_ad,
'ad_id': ad_id,
'price': price,
'sending_by': sending_by,
'price_is_changed': price_is_changed,
'accepted': accepted,
'from_send_message': from_send_message,
'winner_or_looser': winner_or_looser,
}))
And my second app.consummer.py
class NotificationConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
print("deconnected")
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
print("notif", text_data_json)
message = text_data_json['message']
from_user = text_data_json['from_user']
to_user = text_data_json['to_user']
users_id = []
users_id.append(from_user)
users_id.append(to_user)
# Send message to room group
for id in users_id:
self.room_group_name = str(id)
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'from_user': from_user,
'to_user': to_user,
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
from_user = event['from_user']
to_user = event['to_user']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message,
'from_user': from_user,
'to_user': to_user,
}))
How to use the same connection for differents consummers? because two differents connections doesn't works. I got error.
I tried many way but no success
You cannot connect the same socket connection to two different consumers
ie
Django-channels documentation says
Channels routers only work on the scope level, not on the level of individual events, which means you can only have one consumer for any given connection.
Routing is to work out what single consumer to give a connection, not how to spread events
from one connection across multiple consumers.
In your browser console write[javascript] the following code and hit enter
var socket = new WebSocket('ws://localhost:8000/ws/user/(?P<user_id>\d+)/$');
var socket2 = new WebSocket('ws://localhost:8000/ws/notification/(?P<room_name>\w+)/$');
and check if both of them connect to django-server; in my perspective both of them should connect since both the consumers are in different apps(ie my-first-app & my-second-app as you mentioned).
from my understanding what you want to do is connect a single socket which would handle user-related stuff on UserConsumer and notification on another NotificationConsumer.
For this you would have to create multiple event-handlers for(Notification alone in the same .consumers.py file) which would get trigger whenever a notification needs to be sent.
Or else you can use your existing implementation but variable "socket2" will handle notification.
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)