Skip to content
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

Initial implementation of DO-attached containers API #3354

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

kentonv
Copy link
Member

@kentonv kentonv commented Jan 17, 2025

Implemented here:

  • Basic lifecycle (start/monitor/destroy/signal)
  • Connecting to a TCP port in the container, either as a raw socket or HTTP.

Not implemented yet:

  • Intercepting outgoing connections from container (listenTcp()).
  • Starting the DO with an already-running container.
  • Hibernating while container is running.
  • vsocks
  • stdio interception
  • Any sort of local testing with workerd (only the production runtime is fully wired up for now).

No methods are implemented yet, but if a container capability is passed in when creating an actor, then `ctx.container` will be populated.
Tests are currently in the internal codebase, since that's where the mock container service lives, but probably some of that should be ported out here later, especially once we figure out how local testing of containers will work.
@kentonv kentonv requested review from gabivlj and shrima-cf January 17, 2025 22:57
@kentonv kentonv requested review from a team as code owners January 17, 2025 22:57
Copy link

The generated output of @cloudflare/workers-types has been changed by this PR. If this is intentional, run bazel build //types && rm -rf types/generated-snapshot && cp -r bazel-bin/types/definitions types/generated-snapshot to update the snapshot. Alternatively, you can download the full generated types:

Full Type Diff

@@ -536,6 +543,7 @@ class DurableObjectState: public jsg::Object {
JSG_METHOD(waitUntil);
JSG_READONLY_INSTANCE_PROPERTY(id, getId);
JSG_READONLY_INSTANCE_PROPERTY(storage, getStorage);
JSG_READONLY_INSTANCE_PROPERTY(container, getContainer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these could probably be JSG_LAZY_READONLY_INSTANCE_PROPERTY


class Fetcher;

class Container: public jsg::Object {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add code comments explaining what a Container is


void visitForGc(jsg::GcVisitor& visitor) {
visitor.visit(destroyReason);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: implement visitForMemoryInfo(...) for the destroyReason field.

}
req.setEnableInternet(options.enableInternet);

IoContext::current().addTask(req.send().ignoreResult());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Might be just a slight bit better to auto& ioContext = IoContext::current() before the auto req = rpcClient->startRequest() just in case the IoContext::current() fails. Not crtical and definitely an edge case tho.


return IoContext::current()
.awaitIo(js, rpcClient->monitorRequest(capnp::MessageSize{4, 0}).send().ignoreResult())
.then(js, [this](jsg::Lock& js) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safer to capture self=JSG_THIS rather than this? It's unlikely that the container will disappear while this is pending but likely best to be safe?

}

jsg::Promise<void> Container::destroy(jsg::Lock& js, jsg::Optional<jsg::Value> error) {
if (!running) return js.resolvedPromise();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If destroy is called multiple times it looks like this will end up sending the destroyRequest multiple times? Also, it ends up creating and returning multiple promises. Once it has been called, it likely should be arranged to return the same js promise instance back.

}

jsg::Promise<void> Container::monitor(jsg::Lock& js) {
JSG_REQUIRE(running, Error, "monitor() cannot be called on a container that is not running.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What, if anything, should happen if this is called after "destroy()" is called but the destroy promise is still pending?

}

void Container::signal(jsg::Lock& js, int signo) {
JSG_REQUIRE(signo > 0 && signo <= 64, TypeError, "Invalid signal number.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should be a RangeError?

running = false;
KJ_IF_SOME(d, destroyReason) {
jsg::Value error = kj::mv(d);
destroyReason = kj::none;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: this pattern still bugs. It's so easy for forget to set the var to kj::none after moving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants