forked from testcontainers/testcontainers-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(reliability): integrate the ryuk container for better container …
…cleanup (testcontainers#314) > [!NOTE] > Editor's note from @totallyzen: > After a thorough discussion between @santi @alexanderankin, @kiview and @totallyzen on the [slack](https://testcontainers.slack.com/archives/C04SRG5AXNU/p1710156743640249) we've decided this isn't a breaking change in api, but a modification in behaviour. Therefore not worth a 5.0 but we'll release it under 4.x > If this did end up breaking your workflow, come talk to us about your use-case! **What are you trying to do?** Use Ryuk as the default resource cleanup strategy. **Why should it be done this way?** The current implementation of tc-python does not handle container lifecycle management the same way as other TC implementations. In this PR I introduce Ryuk to be the default resource cleanup strategy, in order to be better aligned with other TC implementations. Ryuk is enabled by default, but can be disabled with the `TESTCONTAINERS_RYUK_DISABLED` env variable (which follows the behavior of other TC implementations). Ryuk behavior can further be altered with the `TESTCONTAINERS_RYUK_PRIVILEGED`, `RYUK_CONTAINER_IMAGE` and `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` (also following the same conventions from other implementations). Documentation of these env variables is added to the README in the root of the repo. The implementation uses a singleton container that starts an instance of Ryuk and stores it on class / module level, so that it is reused within a process. This follows the same pattern as in other languages, and Ryuk behaviour itself is implemented the exact same way as other implementations. **BREAKING CHANGE** `__del__` support is now removed, in order to better align with other TC implementations. From the comments in the `__del__` implementation, it seems like this was added as a final attempt to cleanup resources on exit/garbage collection. This leaves three ways to cleanup resources, which has better defined behaviors and follows modern patterns for resource management in Python: Method 1: Context Manager Init/cleanup through a context manager (__enter__/__exit__): ``` with DockerContainer("postgres") as container: # some logic here # end of context: container is killed by `__exit__` method ``` Method 2: Manual start() and stop() ``` container = DockerContainer("postgres").start() # some logic here container.stop() ``` Method 3: Ryuk ``` container = DockerContainer("postgres").start() # some logic here # You forget to .stop() the container, but Ryuk kills it for you 10 seconds after your process exits. ``` _Why remove `__del__`?_ According to the previous maintainer of the repo, it has been causing “[a bunch of issues](https://github.com/testcontainers/testcontainers-python/pull/314#discussion_r1185321083)”, which I have personally experienced while using TC in a Django app, due to the automatic GC behavior when no longer referencing the container with a variable. E.g. if you instantiate the container in a method, only returning the connection string, the Python garbage collector will automatically call `__del__` on the instance at the end of the function, thus killing your container. This leads to clunky workarounds like having to store a reference to the container in a module-level variable, or always having to return a reference to the container from the function creating the container. In addition, the gc behaviour is not consistent across Python implementations, making the reliance on `__del__` flaky at best. Also, having the __del__ method cleanup your container prevents us from implementing `with_reuse()` (which is implemented in other TC implementations) in the future, as a process exit would always instantly kill the container, preventing us to use it in another process before Ryuk reaps it. **Next steps** Once this PR is accepted, my plan is to implement the `with_reuse()` functionality seen in other implementations, to enable faster / instant usage of existing containers. This is very useful in simple testing scenarios or local development workflows using hot reload behaviour. The `with_reuse()` API requires the removal of `__del__` cleanup, as otherwise the container would not be available for reuse due to the GC reaping the container as soon as the process exits. **Other changes** - Adds “x-tc-sid=SESSION_ID” header to the underlying Docker API client with the value of the current session ID (created on module init), in order to enable Testcontainers Cloud to operate in “Turbo mode” testcontainers#314 (comment) - Adds labels `org.testcontainers.lang=python` and `org.testcontainers.session-id=SESSION_ID`to the containers created by TC - As mentioned above, the env variables TESTCONTAINERS_RYUK_DISABLED, TESTCONTAINERS_RYUK_PRIVILEGED, RYUK_CONTAINER_IMAGE and TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE are now used for customizing tc-python behavior. --------- Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Co-authored-by: Balint Bartha <39852431+totallyzen@users.noreply.github.com>
- Loading branch information
1 parent
ae7b163
commit fdadee4
Showing
8 changed files
with
153 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from typing import Optional | ||
from uuid import uuid4 | ||
|
||
from testcontainers.core.config import RYUK_IMAGE | ||
|
||
SESSION_ID: str = str(uuid4()) | ||
LABEL_SESSION_ID = "org.testcontainers.session-id" | ||
LABEL_LANG = "org.testcontainers.lang" | ||
|
||
|
||
def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str]: | ||
if labels is None: | ||
labels = {} | ||
labels[LABEL_LANG] = "python" | ||
|
||
if image == RYUK_IMAGE: | ||
return labels | ||
|
||
labels[LABEL_SESSION_ID] = SESSION_ID | ||
return labels |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from testcontainers.core import container | ||
from testcontainers.core.container import Reaper | ||
from testcontainers.core.container import DockerContainer | ||
from testcontainers.core.waiting_utils import wait_for_logs | ||
|
||
|
||
def test_wait_for_reaper(): | ||
container = DockerContainer("hello-world").start() | ||
wait_for_logs(container, "Hello from Docker!") | ||
|
||
assert Reaper._socket is not None | ||
Reaper._socket.close() | ||
|
||
assert Reaper._container is not None | ||
wait_for_logs(Reaper._container, r".* Removed \d .*", timeout=30) | ||
|
||
Reaper.delete_instance() | ||
|
||
|
||
def test_container_without_ryuk(monkeypatch): | ||
monkeypatch.setattr(container, "RYUK_DISABLED", True) | ||
with DockerContainer("hello-world") as cont: | ||
wait_for_logs(cont, "Hello from Docker!") | ||
assert Reaper._instance is None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters