Container lifecycle (beta)

Modal lets you register code that should be run on container start-up and tear-down. To define either of these lifecycle functions, you need to make your function a member of a class, and then override __enter__, __exit__, __aenter__, or __aexit__ for the class, as appropriate.

Note that the syntax and behavior for these functions is similar to Python context managers.

__enter__ and __aenter__

The container enter handler is called when a new container is started. This is useful for doing one-time initialization, such as loading model weights or importing packages that are only present in that image.

To use with a synchronous Modal app, make your function a member of a class, and override __enter__ for the class:

class Model:
    def __enter__(self):
        self.model = pickle.load(open("model.pickle"))

    @stub.function
    def predict(self, x):
        return self.model.predict(x)


@stub.local_entrypoint
def main():
    Model().predict.call(x)

When working with an asynchronous Modal app, you may use __aenter__ instead:

class Processor:
    async def __aenter__(self):
        self.cache = await load_cache()

    @stub.function
    async def run(self, x):
        return await do_some_async_stuff(x, self.cache)


@stub.local_entrypoint
async def main():
    await Processor().run.call(x)

__exit__ and __aexit__

The container exit handler is called when a container is about to exit. Just like __exit__ for a context manager, this function takes three additional arguments, exc_type, exc_value, and traceback, that describe the exception that was raised. If the container exited normally, these values are all None.

The exit handler is useful for doing one-time cleanup, such as closing a database connection or saving intermediate results.

The exit handler is also called if the container was stopped due to a user action, or the app exited due to an exception. in this case, the exception type will be a KeyboardInterrupt. Note that the exit handler is given a fixed grace period of 4 minutes to exit. If the function takes longer than that, a SIGKILL is issued to the process.

To use with a synchronous Modal app, make your function a member of a class, and override __aexit__ for the class:

class ETLPipeline:
    def __enter__(self):
        import psycopg2
        self.connection = psycopg2.connect(os.environ["DATABASE_URI"])

    @stub.function
    def run(self):
        # Run some queries
        pass

    def __exit__(self, exc_type, exc_value, traceback):
        self.connection.close()


@stub.local_entrypoint
async def main():
    ETLPipeline().run.call()

When working with an asynchronous Modal app, you may use __aexit__ instead.