Skip to content

kiwipy/rmq related modules into independent module#297

Merged
unkcpz merged 22 commits intoaiidateam:devfrom
unkcpz:rmq-out
Feb 27, 2025
Merged

kiwipy/rmq related modules into independent module#297
unkcpz merged 22 commits intoaiidateam:devfrom
unkcpz:rmq-out

Conversation

@unkcpz
Copy link
Member

@unkcpz unkcpz commented Dec 12, 2024

The refactoring is targeting to decouple the dependencies of using kiwipy+rmq as the communicator for the process control.
By forming a Coordinator protocol contract, the different type of rmq/kiwipy related codes are removed out from plumpy logic. The new contract also pave the way to make it clearly show how a new type coordinator can be implemented (future examples will be the tatzelwurm a task broker that has scheduler support and file based task broker require no background service).
For the prototype of how a coordinator should look like, the MockCoordinator in tests/utils is the coordinator that store things in memory, and can serve as the lightweight ephemeral daemon without persistent functionality.

Another major change here is hand write the resolver of future by mimic how tho asyncio does for wrapping concurrent.futures.Future into asyncio.Future. I use the same way to convert asyncio.Future into concurent.futures.Future (which is the kiwipy.Future as alias).

  • move the aio_pika import lazily by moving the rmq exceptions to rmq module, this can increase the performance of import aiida; aiida.orm.
  • CancellableAction using composite to behave as a Future like object.
  • use asyncio.Future in favor of alias plumpy.Future and
  • use concurrent.futures.Future instead of alias kiwipy.Future.
  • Hand write _chain and _copy_future since we can not just rely on the API of asyncio that is not exposed.
  • Forming the coordinator/Communicator protocol.
  • Just forming the coordinator/Coordinator protocol and wrap rmq/communicator as a coordinator that not require changs in kiwipy.
  • Mock the coordinator for unit test.
  • test against aiida-core see what need to be changed there and improve here. (Decouple broker and coordinator interface aiida-core#6675)
  • The API for plumpy process can be more compact instead of using kiwipy/rmq "subscriber" concept. (how to replace rpc pattern??) out of the scope of this PR.
  • n future PR: use async RemoteProcessController in aiida-core. (Make async version of ProcessController exposed and used in aiida-core #315)
  • TODO: Rename and try to combine the task/rpc subscriber.
  • remove the first unused argument of message_receiver and ProcessLauncher.
    Rmq move out as an extra module unkcpz/plumpy#6

@unkcpz unkcpz marked this pull request as draft December 12, 2024 10:58
@unkcpz unkcpz mentioned this pull request Dec 12, 2024
@codecov
Copy link

codecov bot commented Dec 14, 2024

Codecov Report

Attention: Patch coverage is 78.23640% with 116 lines in your changes missing coverage. Please review.

Please upload report for BASE (dev@f760b4a). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/plumpy/rmq/futures.py 46.99% 44 Missing ⚠️
src/plumpy/processes.py 71.92% 25 Missing ⚠️
src/plumpy/rmq/process_control.py 82.53% 18 Missing ⚠️
src/plumpy/controller.py 69.57% 7 Missing ⚠️
src/plumpy/message.py 94.02% 7 Missing ⚠️
src/plumpy/coordinator.py 73.69% 5 Missing ⚠️
src/plumpy/futures.py 70.59% 5 Missing ⚠️
src/plumpy/rmq/coordinator.py 91.12% 4 Missing ⚠️
src/plumpy/rmq/communications.py 92.86% 1 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff           @@
##             dev     #297   +/-   ##
======================================
  Coverage       ?   89.46%           
======================================
  Files          ?       28           
  Lines          ?     3196           
  Branches       ?        0           
======================================
  Hits           ?     2859           
  Misses         ?      337           
  Partials       ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@unkcpz unkcpz force-pushed the rmq-out branch 5 times, most recently from fc52fcd to c0a8bbd Compare December 14, 2024 23:47
@unkcpz unkcpz changed the base branch from dev to master December 15, 2024 00:22
@unkcpz unkcpz changed the base branch from master to dev December 15, 2024 00:22
@unkcpz unkcpz force-pushed the rmq-out branch 9 times, most recently from 4960836 to 47ac8e4 Compare December 17, 2024 20:28
@unkcpz unkcpz changed the title Move kiwipy/rmq related modules into a specific module kiwipy/rmq related modules into independent module Dec 17, 2024
@unkcpz unkcpz force-pushed the rmq-out branch 7 times, most recently from 85fc72a to da644ac Compare December 17, 2024 21:53
@unkcpz unkcpz force-pushed the rmq-out branch 2 times, most recently from 49991fe to f698369 Compare February 24, 2025 09:29
@unkcpz
Copy link
Member Author

unkcpz commented Feb 24, 2025

Rebase with uv.lock, the lines changed are much less. It is ready for another review @agoscinski

)
communicator._loop.set_debug(True)
comm._loop.set_debug(True)
coordinator = RmqCoordinator(comm)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a typical example how I wrap the original rmq communicator into the Coordinator interface.

Comment on lines +13 to +38
def hook_rpc_receiver(
self,
receiver: 'Receiver',
identifier: 'ID_TYPE | None' = None,
) -> Any: ...

def hook_broadcast_receiver(
self,
receiver: 'Receiver',
subject_filters: list[Hashable | Pattern[str]] | None = None,
sender_filters: list[Hashable | Pattern[str]] | None = None,
identifier: 'ID_TYPE | None' = None,
) -> Any: ...

def hook_task_receiver(
self,
receiver: 'Receiver',
identifier: 'ID_TYPE | None' = None,
) -> 'ID_TYPE': ...

def unhook_rpc_receiver(self, identifier: 'ID_TYPE | None') -> None: ...

def unhook_broadcast_receiver(self, identifier: 'ID_TYPE | None') -> None: ...

def unhook_task_receiver(self, identifier: 'ID_TYPE') -> None: ...
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rename the interfaces to distinguish the coordinator from rmq communicator.

Copy link
Contributor

@agoscinski agoscinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to pause review. Posting what I have so far now.


# This class not conform with typing of ProcessController protocol.
# Does't matter too much, since this controller is not directly used as the controller by downstream.
class RemoteProcessController:
Copy link
Contributor

@agoscinski agoscinski Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would inherit from the protocol to make it explicit that we implement it

class RemoteProcessController(ProcessController):

It defeats part of the idea Protocol that you do not need inheritance, but from this file it is not clear that this is implemented. Still you get the advantage (or disadvantage, depending how one sees it) of moving the check that the class is correctly implemented to static type checking phase and not runtime is for abstract classes. (EDIT: The same is True for abstract classes I really don't get the benifit)

Further reading: https://peps.python.org/pep-0544/#explicitly-declaring-implementation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I inherit for both, these two controllers do not need to be generic. Make a lot sense to just explicitly declaring the implementation.

tests/utils.py Outdated
Snapshot = collections.namedtuple('Snapshot', ['state', 'bundle', 'outputs'])


class MockCoordinator:
Copy link
Contributor

@agoscinski agoscinski Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have a test that this actually implements the protocol. By inheriting from the Protocol the type checker would pick it up but I am not sure if the type checker is running on the tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just give it a try. For MockCoordinator, sure, didn't see the power of protocol. When it comes to RmqCoordinator where I want to have a generic typing support for the inner communicator (in aiida, it is LoopCommunicator, in plumpy, there are RmqCommunicator and RmqThreadCommunicator).
I am sure there should be ways to workaround this:

tests/rmq/__init__.py:19: in <module>
    class RmqCoordinator(Generic[U], Coordinator):
../../.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/abc.py:106: in __new__
    cls = super().__new__(mcls, name, bases, namespace, **kwargs)
E   TypeError: Cannot create a consistent method resolution
E   order (MRO) for bases Generic, Coordinator

I believe this can make our life a bit uneasy when it comes to define more generic types that need to work with static type checking.

It is a valid point that the test is better to add, so I add runtime_checkable to Coordinator and have two tests for MockCoordinator and RmqCoordinator respectively. (4242057)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that was my skill issue. What I should do is:

class RmqCoordinator(Coordinator, Generic[U]):
    ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the inheritance order important or what was the issue something else?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem was Coordinator as a protocol is already inherited from Generic. Looks it again, I still don't think it is a good way to put two parent class here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note for my self.
In the current situation, there is no actually benefit to use protocol over abc class. I can just argue it is a bit more "flexible", with the cost of losing the explicity. The advantage only shows up when there is another implementation that has its own way to use its own "Coordinator" then making RmqCoordinator to work with that case without inherit can be only possible with protocol. But we never need to get there.

Therefore, the style will be using protocol but explicitly inherit from it for subclass.

return new_future


# XXX: this required in aiida-core, see if really need this unwrap.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you change the logic somewhere that it does not need to be unwrapped? I am actually not sure anymore why we have these nested Futures.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place that uses this unwrap is in aiida_core/src/aiida/engine/processes/control.py. This piece of shitty workaround was actually add by me :( aiidateam/aiida-core@cd0d15c what a shame.

I said the workaround "should and can be removed". I'll give it a look on the aiida-core side after this PR.

Copy link
Contributor

@agoscinski agoscinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments for future changes in the dev branch to PR #319

@unkcpz unkcpz merged commit a4b8752 into aiidateam:dev Feb 27, 2025
7 checks passed
@unkcpz
Copy link
Member Author

unkcpz commented Feb 27, 2025

It is rebased and merged to dev branch. Many many thanks for the review @agoscinski!

@unkcpz unkcpz deleted the rmq-out branch February 27, 2025 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants