edelvalle / reactor

Phoenix LiveView but for Django
635 stars 26 forks source link

Question about the end of the component life cycle #52

Open dannwise opened 1 year ago

dannwise commented 1 year ago

Hi @edelvalle, thanks for this very nice library!

I have a question: is there a way to receive some sort of signal when the component is disconnected?

I have tried to override the destroy method on the component but it seems to have no effect when WS connection is closed.

Thanks in advance!

edelvalle commented 1 year ago

Oh you are right, there is no signal when the component disconnects in the server side. It just gets deleted from memory, a hack without modifying reactor would be to use the __del__() method that gets called just before the component is garbage collected, but this is not a very good idea because it will be triggered when the component is rendering from the server side the first time and when it disconnects, and it is non deterministic as the garbage collection can happen at any time after the component is no longer in use.

Maybe I should add that, how would you like to use this feature?

dannwise commented 1 year ago

Hi @edelvalle, thanks for taking your time to answer this.

My initial thought was around being able to sync components initial state between browsers and page loads without using records in the database.

I got as far as keeping everything in sync keeping track of the created components in memory (with a manager). New instances would then look for a reference of its initial state based on existing instances. The problem is: as things are stored in memory, whenever components disconnect I found no way of "handling" their disconnection. So their instances keep living inside the manager. Thus, components state is only reset after server restart.

In a more "pratical" way, what I was aiming was: whenever there is at least one instance of the component already in place, new instances would be created copying its initial state. But when all instances disconnect, the initial state is reset to a default. So, thinking in a counter, its initial amount would be 0 if it is the first instance, otherwise it would copy the other instances current amount.

I know the described use case is very exploratory, as I was learning about reactor and testing its capabilities. But I think that being able to execute something at the end of the lifecycle of the component would be beneficial. It could serve to other purposes such as deleting a record from DB that is no longer needed or for a trade-off between memory and DB hits in larger scenarios, where DB operations could be postponed to the end of the cycle.

Please, let me know your thoughts about it and sorry as it got quite extensive.

edelvalle commented 1 year ago

Ok, I don't have a direct solution for you but I can let you know about a few stuff and maybe you can find your own path and let me know you did it.

Each component has a _subscriptions attribute that is a set[str] where you can express as a property or as a static member what channels do you want to subscribe to.

and you can send messages by using


class Component:
    async def xxx(self):
        await self.reactor.send_to("channel_name", "dispatch_event", id="component_id", command="method_name", args=(...), kwargs={...})

Like this you can subscribe your components to a channel and if they know each other IDs by convention or because you put them in redis or something they can talk to each other.

That's kind of my idea here, let me know....

dannwise commented 1 year ago

Hey @edelvalle, didn't have much time last couple of days and I wanted to make sure to take a more in depth look into reactor's code before answering you.

I think I may have mislead you with my previous explanation. Communication between components was easy to set up from the start. What I was aiming was really having a way of being aware of component disconnection for being able to handle custom code if needed. And I think I managed to find the solution, but, of course, would like your thoughts. If you agree with it, I'll be happy to submit a PR. I really liked reactor and would like to contribute to it in any way I can.

So, here is what needs to be changed:

In consumers.py

class ReactorConsumer(AsyncJsonWebsocketConsumer):
    ...

    async def disconnect(self, code):
        await super().disconnect(code)
        for component in self.repo.components.values():
            await component.destroy()

By doing so, the destroy method of the component would be called when it is disconnected, allowing for custom code execution.

The only doubt I have is if destroy is the best method to use or if a new method should be added only for this purpose. What do you think of this?

Also, please let me know if I am ignoring something with this approach, as you understand reactor's core far better than me.

dannwise commented 1 year ago

Hey @edelvalle, did you have a chance to look at this?