State Example

This example showcases how to interact with in-memory shared state using middlewares in Garden.

In the Gardener class, you specify the middlewares you wish to utilize. In this instance, we are employing the StateMiddleware with configured details. Additionally, two tasks are set up to perform some basic usage of shared states.

When using StateMiddleware, an object named state will be mounted under each task's objects property. The objects are also accessible under the Gardener itself.

In this example, one task is configured to set up a listener to the system that monitors state changes. Another task reads and writes the state simultaneously. This task is set to run 20 times repeatedly with 500 replicas of itself. With each run, it increments the count number by one. Thus, in the end, a total of 10000 counts is expected.

The StateMiddleware.config method can take two parameters. One is for setting up the initial state when the program runs, and the other is for specifying a path to a JSON file to store the runtime state. If the file path exists, it will attempt to load it when the Gardener starts. When it shuts down, it saves all the state into that file. In this way, you can have the runtime state persistent across different runs without introducing a database or a store.

NOTE: Since it's in JSON format, you cannot save reference objects in a loop or anything that cannot be stringified as a JSON string.

Code

# examples/use_state.py

from garden import Gardener, Hedgehog
from garden.middlewares.state import StateMiddleware, State


def my_listener(state):
    print(f'State changed to {state}')


class TestGardener(Gardener):
    '''
    TestGardener shows how to use shared state.
    '''

    middlewares = [
        StateMiddleware.config(
            initial_state={'count': 0}, state_file='./state.json'
        ),
    ]

    @Gardener.task('Test Task - Count', repeat=20, replica=500, delay=1)
    async def test_task(self, task: Hedgehog):
        state: State = task.objects.state
        count = state.get_state().get('count')
        state.set_state({'count': count + 1})

    @Gardener.task('Test Task - Listener', repeat=False)
    async def test_task(self, task: Hedgehog):
        task.objects.state.add_listener(my_listener)


if __name__ == '__main__':
    TestGardener(name='TG').start()

Output

> python examples/use_state.py
2025-04-01 07:51:52 INFO::Hedgehog [Test Task - Count [replica:1]] created
2025-04-01 07:51:52 INFO::Hedgehog [Test Task - Count [replica:2]] created
2025-04-01 07:51:52 INFO::Hedgehog [Test Task - Count [replica:3]] created
            ...
State changed to {'count': 9998}
State changed to {'count': 9999}
State changed to {'count': 10000}
            ...
2025-04-01 07:51:53 INFO::Hedgehog [Test Task - Count [replica:499]] terminated
2025-04-01 07:51:53 INFO::Hedgehog [Test Task - Count [replica:500]] terminated
2025-04-01 07:51:54 INFO::TestGardener [TG] stopped
2025-04-01 07:51:54 INFO::MiddlewareManager deregistering middlewares[StateMiddleware]
2025-04-01 07:51:54 INFO::State state saved to file: ./state.json
2025-04-01 07:51:54 INFO::TestGardener [TG] terminated
2025-04-01 07:51:54 INFO::TestGardener [TG] exited

NOTE: Log content has been trim down to save space.