-
-
Notifications
You must be signed in to change notification settings - Fork 500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: more robust Reusable containers experience #2768
base: main
Are you sure you want to change the base?
Conversation
Removing the sessionID label, it won't be pruned by Ryuk
✅ Deploy Preview for testcontainers-go ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
@stevenh as always, your wise 👀 are more than welcome! |
// can proceed with the creation of the container. | ||
// This is needed because we need to synchronize the creation of the container across | ||
// different test programs. | ||
c, err := p.waitContainerCreationInTimeout(ctx, hash, 5*time.Second) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this as a potential configuration:
- a property:
testcontainers.reuse.search.timeout
, and - an env var:
TESTCONTAINERS_REUSE_SEARCH_TIMEOUT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the PR description says:
We expect this change helps the community, but at the same time warn about its usage in parallel executions, as it could be the case that two concurrent test sessions get to the container creation at the same time, which could lead to the creation of two containers with the same request.
wouldn't we want to accept this limitation and allow for the race condition with the current implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. Added that comment as an idea for a potential follow-up
I wonder if this solves this bug: #2749 I'll do a small code review here as well. Thank you for this! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done an initial pass
err = req.creatingHook(ctx) | ||
if err != nil { | ||
return nil, err | ||
if req.Reuse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Not sure about this, I would expect reused containers to still only persist while in use, is that the intent?
The edge case for shutdown while still in use, is addressed by reaper rework PRs, so it should be safe to remove this if the desired behaviour is to share between test runs that are within a reasonable window.
reuseContainerMx.Lock() | ||
defer reuseContainerMx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: This will lock for the duration of rest of the call, is that the intention?
req.Labels[core.LabelContainerHash] = fmt.Sprintf("%d", hash.Hash) | ||
req.Labels[core.LabelCopiedFilesHash] = fmt.Sprintf("%d", hash.FilesHash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: do we need multiple hashes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is aligned with the Java implementation. We need these two in order to keep track of the added files before the container is started (Files attribute in the container request)
c, err := p.waitContainerCreationInTimeout(ctx, hash, 5*time.Second) | ||
if err != nil && !errdefs.IsNotFound(err) { | ||
// another error occurred different from not found, so we return the error | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Can we wrap so we know where it came from
} | ||
|
||
var resp container.CreateResponse | ||
if req.Reuse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: combine the two if's if the above isn't removed, based on previous question
if !ok { | ||
// Calculate the hash of the file content only if it is a file. | ||
fileContent, err = os.ReadFile(f.HostFilePath) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bug: this will lead to unexplained issues.
// the hash will be zero to avoid breaking the hash calculation. | ||
if f.Reader != nil { | ||
fileContent, err = io.ReadAll(f.Reader) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bug: this will lead to unexplained issues.
} | ||
} else { | ||
ok, err := isDir(f.HostFilePath) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bug: this will lead to unexplained issues.
) | ||
|
||
// containerHash represents the hash of the container configuration | ||
type containerHash struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: are two independent hashes needed.
// Read the file content to calculate the hash, if there is an error reading the file, | ||
// the hash will be zero to avoid breaking the hash calculation. | ||
if f.Reader != nil { | ||
fileContent, err = io.ReadAll(f.Reader) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggest: Use streaming hash instead of reading files into memory.
@kiview I'm converting to draft until we discuss internally about the implications of switching from the container name-based approach to a hash-based approach. Will share here the discussion once it takes place. |
if req.Reuse { | ||
// Remove the SessionID label from the request, as we don't want Ryuk to control | ||
// the container lifecycle in the case of reusing containers. | ||
delete(req.Labels, core.LabelSessionID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed to not remove the sessionId but an specific label when ryuk is enabled. TBD
What does this PR do?
This PR modifies the way the Reuse mode is implemented in the project, taking tc-java as the implementation reference. For that:
github.com/mitchellh/hashstructure/v2
for the hash of the container request, with the following exceptions:hash:"ignore"
to identify them all.Files
field contains a reference to a directory in the host, it won't participate in the calculation of the hash, as we don't want to traverse the directory getting the hash for the directory tree.Files
field, we'll read their bytes and use them get a hash, which will be added to the struct hash.org.testcontainers.hash
: the hash of the container request.org.testcontainers.copied_files.hash
: the hash of the files copied to the container using theFiles
field in the container request.ReuseOrCreateContainer
from the provider struct has been deprecated for removal in the upcoming releases.Why is it important?
Create a more consistent experience around reuse, based on the container state and not forcing the user to add a container name, that can be reused by mistake in a totally different container request.
We expect this change helps the community, but at the same time warn about its usage in parallel executions, as it could be the case that two concurrent test sessions get to the container creation at the same time, which could lead to the creation of two containers with the same request.
Related issues
Docs
Render URLs:
Follow-ups
Please try out this branch, and read the docs so that they can be improved if needed 🙏