Thursday, March 1, 2018

Re: Channels 2: Consumer running twice when two users simultaneously share socket

Can you clarify what you mean by "two users connected to the same socket"? If you have two websockets connected, you'll have two copies of your consumer running, one for each socket.

Andrew

On Thu, Mar 1, 2018 at 7:52 AM, lakeshow <sjung2393@gmail.com> wrote:
I am running into an issue using Channels where if two users are connected to the same socket, the consumer will run twice. When a single user is connected, only once.  
So in practice: when I have two users chatting with each other over channels, a single sent message will be run through the consumer twice, causing it to save twice to the database. However, if there is only one participant in the channel, the message will run through the consumer once and save once as expected.  

What might be the issue?  

Here is the consumer:  

class ChatConsumer(AsyncJsonWebsocketConsumer):  
   
...
    async
def receive_json(self, content):
       
"""
        Called when we get a text frame. Channels will JSON-decode the payload
        for us and pass it as the first argument.
        """

       
# Messages will have a "command" key we can switch on
        command
= content.get("command", None)
        recipient
= content.get("recipient", None)
        sender
= self.scope["user"]
       
try:
           
if command == "join":
               
# Make them join the room
                await
self.join_room(content["room"])
                previous_message_list
= await get_saved_messages(recipient, sender)
               
for msg in previous_message_list:
                    await
self.send_json({
                       
"msg_type": MSG_TYPE_MESSAGE,
                       
"room": content["room"],
                       
"username": msg.sender.user.username,
                       
"message": msg.message,
                   
},)
           
elif command == "leave":
               
# Leave the room
                await
self.leave_room(content["room"])
           
elif command == "send":
                await
self.send_room(
                    content
["room"],
                    content
["message"],
                    content
["recipient"]
               
)
       
except ClientError as e:
           
# Catch any errors and send it back
            await
self.send_json({"error": e.code})  
   
...
    async
def send_room(self, room_id, message, recipient):
       
"""
        Called by receive_json when someone sends a message to a room.
        """

       
# Check they are in this room
       
if room_id not in self.rooms:
           
raise ClientError("ROOM_ACCESS_DENIED")
       
# Get the room and send to the group about it
        room
= await get_room_or_error(room_id, self.scope["user"])
        await
self.channel_layer.group_send(
            room
.group_name,
           
{
               
"type": "chat.message",
               
"room_id": room_id,
               
"username": self.scope["user"].username,
               
"message": message,
               
"recipient": recipient,
           
}
       
)  
   
...
    async
def chat_message(self, event):
       
"""
        Called when someone has messaged our chat.
        """

       
# Send a message down to the client
       
# Save message object
        await save_message_object
(
            sender
=event["username"],
            message
=event["message"],
            recipient
=event["recipient"]
       
)
        await
self.send_json(
           
{
               
"msg_type": MSG_TYPE_MESSAGE,
               
"room": event["room_id"],
               
"username": event["username"],
               
"message": event["message"],
           
},
       
)  

So something is causing the last function--def chat_message() to run twice and I think it's in the consumer and not related to the client because sent messages will log to the console only once--as soon as the messages are sent over socket.send, there will be two of the same Message objects saved onto the db.

Here is code relating to the actual save. It is in utils.py and is decorated using sync_to_async in order to call the sync func from an async consumer:  

@database_sync_to_async  def save_message_object(sender, message, recipient):      recipient = Profile.objects.get(user__username=recipient)      sender = Profile.objects.get(user__username=sender)      m = Message(sender=sender, message=message, recipient=recipient)      m.save()      print(datetime.datetime.now())  

--
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/1a8b04b5-088e-41bd-a0f1-53d326f5f8e9%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/CAFwN1urP471Cc%2BZSYAiW02Q0wnaoQNMgwmUgasg4HJudn_gqUw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment