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.