ronny-rentner / UltraDict

Sychronized, streaming Python dictionary that uses shared memory as a backend
Apache License 2.0
272 stars 25 forks source link

Dict is not being shared amongst gunicorn workers #29

Closed laxuscullen closed 4 months ago

laxuscullen commented 4 months ago
Using Starlette framework.

# this is assigned to a global variable ultra on startup / or just declared at the top
ultra = UltraDict(name="room_chats", create=None, recurse=True)```

and then i am trying to store websockets inside the dict. 

sample code:
```py
# this is on connect
if ultra.get(f"room_chat_{room_id}") is None:
            ultra[f"room_chat_{room_id}"] = set()

ultra[f"room_chat_{room_id}"].add(websocket)

# then in a broadcast method
for connection in ultra[f"room_chat_{room_id}"]:
...

if i print the ultra dict, it only shows one item but sometimes will show multiple connections if same worker gets assigned to those the websocket connections.

{'room_chat_1': {<starlette.websockets.WebSocket object at 0x10740d490>}}

Gunicorn command: gunicorn test:app --name asgi --log-level debug -w 10 --threads 10 -k uvicorn.workers.UvicornH11Worker

Also, on shutdown:

async def shutdown_stuff():
    UltraDict.unlink_by_name("room_chats", ignore_errors=True)
    UltraDict.unlink_by_name('room_chats_memory', ignore_errors=True)

It throws error: FileNotFoundError: [Errno 2] No such file or directory: '/room_chats'

what am i doing wrong? please let me know if any additional info is required.

laxuscullen commented 4 months ago

@ronny-rentner

ronny-rentner commented 4 months ago

Hey, after some quick web search I am not sure if a websocket can be serialized (pickled). Only objects that can be serialized can be shared through UltraDict. What exactly do you want to achieve by sharing those objects?

I can confirm that there's a bug with arrays and sets. To work around it, try using:

ultra[f"room_chat_{room_id}"].add(websocket)
ultra[f"room_chat_{room_id}"] = ultra[f"room_chat_{room_id}"] 

This has worked for me locally for a set. I haven't tested websockets.

Regarding the cleanup, it's happening automatically, so it might have already happened. unlink_by_name() is only useful when your process crashes and leaves back the shared memory without cleaning. When you are on Linux, you should be able to see the shared memory in your file system under /dev/shm. Will let you know once there's a bug fix.

laxuscullen commented 4 months ago

hey @ronny-rentner, thanks for the reply mate. Glad I could help in finding out an obscure bug :) Due to this bug it did not throw the error when trying to serialize/pickle the websocket. Now, using your workaround, it does not let me store the websocket. I was aware that we cannot pickle websocket but this lib worked probably because it was storing it inside a normal set. Storing sockets in normal sets work but of course it's not shared amongst workers. Any idea?

venv/lib/python3.11/site-packages/UltraDict/UltraDict.py", line 726, in append_update marshalled = self.serializer.dumps((not delete, key, item)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: Can't pickle local object '_createenviron.<locals>.encode'

My usecase is to broadcast to all connected sockets. I cannot find a proper way to do it. I see people mentioning to use redis pub/sub but holy shit its hard to understand how to incorporate it. I am just to dumb to understand it.

So decided to give this a shot, but it seems ultradict wont accomplish it either.

laxuscullen commented 4 months ago

@ronny-rentner ?

laxuscullen commented 4 months ago

No worries, thanks for the help. Edit: For anyone else reading this: Ultradict cannot be used for websockets. Use broadcaster for this purpose, it works!

ericyue commented 4 months ago

I can confirm that there's a bug with arrays and sets. To work around it, try using:

I meet this bug too . is there a elegant way to solve this ?

laxuscullen commented 3 months ago

I can confirm that there's a bug with arrays and sets. To work around it, try using:

I meet this bug too . is there a elegant way to solve this ?

He clearly explained a workaround?