Giter Site home page Giter Site logo

signal-slot's Introduction

signal-slot

tests Downloads

Qt-like event loops, signals and slots for communication across threads and processes in Python.

Installation

pip install signal-slot-mp

Linux, macOS, and Windows are supported.

Overview

signal-slot enables a parallel programming paradigm inspired by Qt's signals and slots, but in Python.

The main idea can be summarized as follows:

  • Application is a collection of EventLoops. Each EventLoop is an infinite loop that occupies a thread or a process.
  • Logic of the system is implemented in EventLoopObjects that live on EventLoops. Each EventLoop can support multiple EventLoopObjects.
  • EventLoopObjects can emit signals. A signal "message" contains a name of the signal and the payload (arbitrary data).
  • Components can also connect to signals emitted by other components by specifying a slot function to be called when the signal is received by the EventLoop.

Usage example

import time
import datetime
from signal_slot.signal_slot import EventLoop, EventLoopObject, EventLoopProcess, Timer, signal

now = datetime.datetime.now

# classes derived from EventLoopObject define signals and slots (actually any method can be a slot)
class A(EventLoopObject):
    @signal
    def signal_a(self):
        ...

    def on_signal_b(self, msg: str):
        print(f"{now()} {self.object_id} received signal_b: {msg}")
        time.sleep(1)
        self.signal_a.emit("hello from A", 42)

class B(EventLoopObject):
    @signal
    def signal_b(self):
        ...

    def on_signal_a(self, msg: str, other_data: int):
        print(f"{now()} {self.object_id} received signal_a: {msg} {other_data}")
        time.sleep(1)
        self.signal_b.emit("hello from B")

# create main event loop and object of type A
main_event_loop = EventLoop("main_loop")
a = A(main_event_loop, "object: a")

# create a background process with a separate event loop and object b that lives on that event loop
bg_process = EventLoopProcess(unique_process_name="background_process")
b = B(bg_process.event_loop, "object: b")

# connect signals and slots
a.signal_a.connect(b.on_signal_a)
b.signal_b.connect(a.on_signal_b)

# emit signal from a to kick off the communication
a.signal_a.emit("Initial hello from A", 1337)

# create a timer that will stop our system after 10 seconds
stop_timer = Timer(main_event_loop, 10.0, single_shot=True)
stop_timer.start()

# connect the stop method of the event loop to the timeout signal of the timer
stop_timer.timeout.connect(main_event_loop.stop)
stop_timer.timeout.connect(bg_process.stop)  # stops the event loop of the background process

# start the background process
bg_process.start()

# start the main event loop
main_event_loop.exec()

# if we get here, the main event loop has stopped
# wait for the background process to finish
bg_process.join()

print(f"{now()} Done!")

The output should roughly look like this:

2022-11-30 01:51:58.943425 object: b received signal_a: Initial hello from A 1337
2022-11-30 01:51:59.944957 object: a received signal_b: hello from B
2022-11-30 01:52:00.945852 object: b received signal_a: hello from A 42
2022-11-30 01:52:01.947599 object: a received signal_b: hello from B
2022-11-30 01:52:02.949214 object: b received signal_a: hello from A 42
2022-11-30 01:52:03.950762 object: a received signal_b: hello from B
2022-11-30 01:52:04.952419 object: b received signal_a: hello from A 42
2022-11-30 01:52:05.953596 object: a received signal_b: hello from B
2022-11-30 01:52:06.954918 object: b received signal_a: hello from A 42
2022-11-30 01:52:07.956701 object: a received signal_b: hello from B
2022-11-30 01:52:08.957755 object: b received signal_a: hello from A 42
2022-11-30 01:52:09.963144 Done!

Implementation details

  • There's not argument validation for signals and slots. If you connect a slot to a signal with a different signature, it will fail at runtime. This can also be used to your advantage by allowing to propagate arbitrary data as payload with appropriate runtime checks.
  • It is currently impossible to connect a slot to a signal if emitter and receiver objects belong to event loops already running in different processes (although it should be possible to implement this feature). Connect signals to slots during system initialization.
  • Signal-slot mechanism in the current implementation can't implement a message passing protocol where only a single copy of the signal is received by the subscribers. Signals are always delivered to all connected slots. Use a FIFO multiprocessing queue if you want only one receiver to receive the signal.

Multiprocessing queues

At the core of the signal-slot mechanism are the queues that are used to pass messages between processes. Python provides a default implementation multiprocessing.Queue, which turns out to be rather slow.

By default we use a custom queue implementation written in C++ using POSIX API that is significantly faster: https://github.com/alex-petrenko/faster-fifo.

Contributing

Local installation for development:

pip install -e .[dev]

Automatic code formatting:

make format && make check-codestyle

Run tests:

make test

Recent releases

v1.0.5
  • Windows support (do not require POSIX-only faster-fifo on Windows)
v1.0.4
  • Use updated version of faster-fifo
v1.0.3
  • Improved logging
v1.0.2
  • Catching queue.Full exception to handle situations where receiver event loop process is killed
v1.0.1
  • Added signal_slot.configure_logger() function to configure a custom logger
v1.0.0
  • First PyPI version

Footnote

Originally designed for Sample Factory 2.0, a high-throughput asynchronous RL codebase https://github.com/alex-petrenko/sample-factory. Distributed under MIT License (see LICENSE), feel free to use for any purpose, commercial or not, at your own risk.

signal-slot's People

Contributors

alex-petrenko avatar wmfrank avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

wmfrank ivan-267

signal-slot's Issues

Installing on Windows 10 64 bit error and solution

I have been getting errors when trying to install the package on Windows with Conda and Python 3.9 in this case such as (paths abbreviated):

PS ...\signal-slot-main> pip install .
Processing ...\signal-slot-main
  Preparing metadata (setup.py) ... error
  error: subprocess-exited-with-error

  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [10 lines of output]
      Traceback (most recent call last):
        File "<string>", line 2, in <module>
        File "<pip-setuptools-caller>", line 34, in <module>
        File "...\setup.py", line 35, in <module>
          packages=setuptools.find_packages(where="./", include="signal_slot*"),
        File "...\discovery.py", line 127, in find
          convert_path(str(where)),
          raise ValueError("path '%s' cannot end with '/'" % pathname)
      ValueError: path './' cannot end with '/'
      [end of output]

Changing the two paths in setup.py from ./ to just . made the install successful, the full modified setup.py was:

import os

import setuptools
from setuptools import setup

with open("README.md", "r") as f:
    long_description = f.read()
    descr_lines = long_description.split("\n")
    long_description = "\n".join(descr_lines)


queue_deps = ["faster-fifo>=1.4.4,<2.0"] if os.name != "nt" else []


setup(
    # Information
    name="signal-slot-mp",
    description="Fast and compact framework for communication between threads and processes in Python using event loops, signals and slots.",
    long_description=long_description,
    long_description_content_type="text/markdown",
    version="1.0.5",
    url="https://github.com/alex-petrenko/signal-slot",
    author="Aleksei Petrenko",
    license="MIT",
    keywords="asynchronous multithreading multiprocessing queue faster-fifo signal slot event loop",
    project_urls={
        "Github": "https://github.com/alex-petrenko/signal-slot",
        "Sample Factory": "https://github.com/alex-petrenko/sample-factory",
    },
    install_requires=queue_deps,
    extras_require={
        "dev": ["black", "isort", "pytest<8.0", "flake8", "pre-commit", "twine"],
    },
    package_dir={"": "."},
    packages=setuptools.find_packages(where=".", include="signal_slot*"),
    include_package_data=True,
    python_requires=">=3.8",
)

I don't have much experience with configuring setup.py so I'm not sure if this is the correct way to implement it for Windows and other platforms, I'm just sharing the experience of what caused the error and what seems to work.

After installing the signal-slot that way, I was able to install Sample Factory on Windows without errors as well (before I was receiving errors on install).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.