Skip to content

move @cloudflare/containers into cloudflare:workers#5247

Open
emily-shen wants to merge 2 commits into
mainfrom
emily/container-class
Open

move @cloudflare/containers into cloudflare:workers#5247
emily-shen wants to merge 2 commits into
mainfrom
emily/container-class

Conversation

@emily-shen

@emily-shen emily-shen commented Oct 6, 2025

Copy link
Copy Markdown
Contributor

This allows people to import the containers class (currently here https://github.com/cloudflare/containers and published to npm) from cloudflare:workers instead.

ContainerEntrypoint should extend DurableObject and inherit everything from that, with some additional container-specific functionality.

I anticipate there will be some bikeshedding on the name.

Testing

  • there is a test in container-client which i've run locally (doesn't run in CI) and seems to pass fine
  • i've also pointed miniflare at this build and also seems to work fine from wrangler dev

@emily-shen emily-shen force-pushed the emily/container-class branch from e03a9f9 to d26d41b Compare November 13, 2025 15:07
@github-actions

github-actions Bot commented Nov 13, 2025

Copy link
Copy Markdown

The generated output of @cloudflare/workers-types matches the snapshot in types/generated-snapshot 🎉

@codspeed-hq

codspeed-hq Bot commented Nov 13, 2025

Copy link
Copy Markdown

CodSpeed Performance Report

Merging #5247 will not alter performance

Comparing emily/container-class (a5b7294) with main (862cb02)

Summary

✅ 53 untouched
⏩ 9 skipped1

Footnotes

  1. 9 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@emily-shen emily-shen force-pushed the emily/container-class branch 2 times, most recently from 70ceead to e503c34 Compare November 13, 2025 16:36
@emily-shen emily-shen requested a review from a team November 13, 2025 17:27
@emily-shen emily-shen force-pushed the emily/container-class branch 5 times, most recently from 9e1712e to bf35d6e Compare November 14, 2025 14:30
Comment thread src/workerd/api/workers-module.c++ Outdated

jsg::JsObject self(args.This());
self.set(js, "ctx", jsg::JsValue(args[0]));
self.set(js, "env", jsg::JsValue(args[1]));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This signature feels... off. You're passing in the ctx and env as arguments but then using the args to get them instead. You're also grabbing the jsg::Lock& using jsg:Lock::from(...) when it can just be acquired by setting it as the first argument accepted by the constructor.

@emily-shen emily-shen Nov 14, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To be totally honest i've just ripped this off the entrypoints directly above, which does say this bit is a hack 🤷 - I'm not very sure what I'm doing here, and how much /what of ContainerEntrypoint should be implemented here in the c++ part. It's basically just a special DO class I want to export from the workers module and it should ideally inherit as much as possible from DOs...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No worries, just doing an early scan through, mostly to get familiar with the case you're enabling. Feel free to ignore these early comments :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not at all, any reviews on this is super appreciated, please let me know if i'm barrelling down the wrong path 😅

Comment thread src/workerd/io/worker.c++ Outdated
Comment on lines +1881 to +1884
if (!FeatureFlags::get(js).getContainerEntrypoint()) {
KJ_FAIL_REQUIRE(
"ContainerEntrypoint is an experimental feature and requires the 'container_entrypoint' compatibility flag.");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (!FeatureFlags::get(js).getContainerEntrypoint()) {
KJ_FAIL_REQUIRE(
"ContainerEntrypoint is an experimental feature and requires the 'container_entrypoint' compatibility flag.");
}
KJ_REQUIRE(FeatureFlags::get(js).getContainerEntrypoint(),
"ContainerEntrypoint is an experimental feature and "
"requires the 'container_entrypoint' compatibility flag.");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

i've ended up removing all the code here - is there a way of accessing compat flags from the typescript implementation?

@emily-shen emily-shen force-pushed the emily/container-class branch 6 times, most recently from 8130439 to 8ff0be4 Compare November 17, 2025 13:41
@emily-shen emily-shen changed the title [draft] start scaffolding ContainerEntrypoint [draft] move @cloudflare/containers into cloudflare:workers Nov 17, 2025
@emily-shen emily-shen force-pushed the emily/container-class branch 4 times, most recently from e7c2c33 to ab21c2c Compare November 17, 2025 16:22
@emily-shen emily-shen force-pushed the emily/container-class branch from ab21c2c to a5b7294 Compare November 17, 2025 17:07
@emily-shen emily-shen changed the title [draft] move @cloudflare/containers into cloudflare:workers move @cloudflare/containers into cloudflare:workers Nov 17, 2025
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
ctx.blockConcurrencyWhile(async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: void ctx.blockConcurrencyWhile(...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

might be able to remove the eslint ignore too

);
});

this.container = ctx.container;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd move above the ctx.blockConcurrencyWhile so the setInactivityTimeout call does not need the ? operator on the ctx.container

@emily-shen emily-shen marked this pull request as ready for review November 17, 2025 17:24
@emily-shen emily-shen requested review from a team as code owners November 17, 2025 17:24
const NOT_LISTENING_ERROR = 'the container is not listening';

function isErrorOfType(e: unknown, matchingString: string): boolean {
const errorString = e instanceof Error ? e.message : String(e);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you can now use Error.isError() method

userProvidedSignal: AbortSignal | undefined,
timeoutMs: number
): AbortSignal {
const controller = new AbortController();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can use AbortSignal.timeout

// we are out of retries
if (attempt === options.retries.limit - 1) {
if (
e instanceof Error &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Error.isError is preferred

@danlapid danlapid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hold with merging until I fully review please.

@kentonv kentonv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am hard against this change in its current form:

Immediate concrete objection

Putting this code into cloudflare:workers will slow down isolate startup for everyone who imports cloudflare:workers even if they don't use containers. cloudflare:workers is needed by almost all applications. A huge containers library doesn't belong in this module.

This objection can be solved by placing the library into its own, separate module.

Broader philosophical objection

Once this lands in the runtime, we have to support it forever. We can't make backwards-incompatible changes at all.

But, this library presents an opinionated framework that is far higher-level than anything we've put into runtime APIs in the past. At the very least, the runtime team would need to carefully review these opinions to decide if we're comfortable with locking them in. Briefly looking at the docs, I already spot some design decisions that I am not very comfortable with -- but I won't go into them here.

Moreover, every change to this library going forward will require review from the runtime team. They will need to be deployed as a runtime release, which is slower that updating a library. Breakages may require rolling back the runtime release, which will not just slow down the containers team but also everyone else trying to land changes in the runtime.

I don't think either the Runtime team or the Container team really wants this.

Alternative direction

If I understand correctly, what the containers team really wants is the ability to update this library without requiring every customer to redeploy their worker to pick up the update.

I think we could create a new mechanism for accomplishing exactly that. We could create a notion of "field-updatable modules". These modules would not be embedded in the runtime itself, but rather would live in nearby storage, where the runtime can fetch them on-demand while loading a worker. Using separate storage would decouple releases from runtime releases, and would allow us to support a large number of libraries deployed this way, such that the runtime team doesn't feel like they are picking winning opinions.

But I think even if we do this, it should be an internal detail of the containers library. That is, app developers should still perceive that they are importing @cloudflare/containers. But that package itself would contain a simple wrapper that imports the field-updatable module and then re-exports it.

This way, you can still make breaking changes to the API in the future, by changing the code in the npm package to point at a different field-updatable module name, which implements the new API. The old name still points at a perfectly backwards-compatible version of the library, to avoid breaking deployed users.

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.

6 participants