feat: concurrency decorator#3089
Conversation
7ce91e8 to
e433b85
Compare
There was a problem hiding this comment.
I'm Bonk, and I've done a quick review of your PR.
This PR adds the @cog.concurrent decorator for configuring async predictor concurrency, with static extraction at build time and runtime enforcement. The decorator integrates cleanly with the existing precedence chain (runtime env > YAML > decorator > default) and properly deprecates the concurrency.max YAML field.
Issues found:
- Missing parser test for qualified alias import pattern — The tree-sitter parser tests cover direct import, qualified import (
import cog), and imported alias (from cog import concurrent as foo), but lack coverage for the qualified alias pattern (import cog as c+@c.concurrent). The streaming decorator has this test (TestStreamingDecoratorQualifiedAliasOptIn) and the concurrent implementation uses the sameattributeIsCogConcurrenthelper, so it likely works — but explicit coverage prevents regressions.
|
I reviewed PR #3089 (add Finding: Missing parser test for Everything else looked solid — the decorator implementation, build-time static extraction, precedence handling, runtime enforcement in Coglet, and deprecation of YAML concurrency are all correctly implemented. |
There was a problem hiding this comment.
I'm Bonk, and I've done a quick review of your PR.
This PR adds the @cog.concurrent decorator for configuring async predictor concurrency, with static extraction at build time and runtime enforcement. Overall the implementation is solid — the decorator, precedence handling, runtime checks in Coglet, and deprecation of YAML concurrency are all correctly implemented.
Issues found:
- Missing parser test for qualified alias import pattern — The previous review already flagged this, but the test still hasn't been added. The tree-sitter parser tests cover direct import, qualified import (
import cog), and imported alias (from cog import concurrent as foo), but lack coverage for the qualified alias pattern (import cog as c+@c.concurrent). The streaming decorator has this test (TestStreamingDecoratorQualifiedAliasOptIn) and the concurrent implementation uses the sameattributeIsCogConcurrenthelper, so it likely works — but explicit coverage prevents regressions. - CodeQL integer conversion warning — The
int64→intconversion atpkg/schema/python/annotations.go:309triggered a CodeQL alert. The code does have an upper bound check at line 306, butint64(math.MaxInt)may not be recognized as a proper bound by static analysis. Extracting the bound to a named constant silences the warning without changing behavior.
|
Posted a batch review to PR #3089 with two suggestions:
|
Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> Signed-off-by: Anish Sahoo <anishsahoo2005@gmail.com>
There was a problem hiding this comment.
I'm Bonk, and I've done a quick review of your PR.
This PR adds the @cog.concurrent decorator for configuring async predictor concurrency, with static extraction at build time and runtime enforcement. Overall the implementation is solid — the decorator, precedence handling, runtime checks in Coglet, and deprecation of YAML concurrency are all correctly implemented.
Issues found:
- Go syntax error in parser tests — There's a duplicate empty function definition at line 1053 that will cause a compilation error.
- CodeQL integer conversion warning — The
int64→intconversion atpkg/schema/python/annotations.go:306still triggers a CodeQL alert. Extracting the bound to a named constant silences the warning without changing behavior.
Merge from #3089 introduced a duplicate TestConcurrentDecoratorIgnoredWhenNotFromCog function signature that caused a syntax error.
|
Posted a batch review to PR #3089 with two issues:
|
|
LGTM |
markphelps
left a comment
There was a problem hiding this comment.
I did a focused pass on the concurrency decorator changes. The happy path looks coherent; these are the spots I think are worth tightening before merge.
| // Generate schema before the Docker build so schema errors fail fast and the | ||
| // schema file is available in the build context. | ||
| var schemaJSON []byte | ||
| predictorInfo, err := generatePredictorMetadata(cfg, dir) |
There was a problem hiding this comment.
This now runs before the needsSchema / schemaFile / skipSchemaValidation switch, so builds that intentionally provide an external schema or skip schema validation can still fail on static source parsing or target lookup. That feels like a behavioral regression for the opt-out paths. Could we either only require this metadata parse when we need decorator-derived concurrency, or make it best-effort/non-fatal when schema validation is explicitly skipped or supplied externally?
There was a problem hiding this comment.
Good catch. The predictor metadata parse still runs before the schema switch, but it is now only fatal when `needsSchema` is true. When schema validation is skipped or an external schema is supplied, a parse failure is logged at debug and we fall back to `nil` (cog.yaml-only concurrency) rather than failing the build. Fixed in cf33465.
| } | ||
| } | ||
|
|
||
| dockerfileCfg, err := configWithDecoratorConcurrency(cfg, predictorInfo) |
There was a problem hiding this comment.
This effective config is what the image build uses, but later the final image label still marshals the original cfg. If concurrency came from @cog.concurrent, the runtime env is right, but anything reading the Cog config label will see concurrency as unset. I think we should either marshal the effective config in the label path or add explicit effective-concurrency metadata so labels and runtime do not drift.
There was a problem hiding this comment.
Fixed in cf33465: the config label now marshals the effective `dockerfileCfg` instead of the original `cfg`, so decorator-derived concurrency in the label stays consistent with the baked `COG_MAX_CONCURRENCY` runtime env.
| ``` | ||
|
|
||
| Models that have an async `run()` function can run concurrently, up to the limit specified by [`concurrency.max`](yaml.md#max) in cog.yaml. Attempting to exceed this limit will return a 409 Conflict response. | ||
| Models that have an async `run()` function can run concurrently. Use `@cog.concurrent(max=N)` to configure the default maximum concurrency for the function: |
There was a problem hiding this comment.
Because this sits under the existing “Added in cog 0.14.0” note, this newly-added @cog.concurrent API now reads as if it has existed since 0.14.0. Could we split this up or reword it, e.g. async runners were added in 0.14.0 and @cog.concurrent was added in the current release? docs/llms.txt should be regenerated after the docs wording changes.
There was a problem hiding this comment.
Reworded in cf33465: the note now reads "Async runners were added in cog 0.14.0. The `@cog.concurrent` decorator was added in cog 0.21.0.", and the decorator paragraph also calls out 0.21.0. Regenerated `docs/llms.txt`.
| > Deprecated: use [`@cog.concurrent(max=N)`](python.md#async-runners-and-concurrency) on your async `run()` method instead. | ||
|
|
||
| This stanza describes the concurrency capabilities of the model. It has one option: | ||
| This stanza describes the concurrency capabilities of the model. It is still supported for backwards compatibility, but new models should use `@cog.concurrent(max=N)`. It has one option: |
There was a problem hiding this comment.
For migration, it would help to explicitly say that this deprecated field still takes precedence over @cog.concurrent. Otherwise users can add the decorator and keep wondering why the YAML value is still the one that gets baked into the image. Maybe add: “If both are set, concurrency.max takes precedence; remove it when migrating to @cog.concurrent.”
There was a problem hiding this comment.
Added in cf33465: "If both `concurrency.max` and `@cog.concurrent(max=N)` are set, `concurrency.max` takes precedence and is the value baked into the image. Remove `concurrency.max` when migrating to `@cog.concurrent`." Regenerated `docs/llms.txt`.
|
|
||
| if fn is None: | ||
| return decorate | ||
| return decorate(fn) |
There was a problem hiding this comment.
The static parser rejects positional usage like @concurrent(2), but the runtime API currently falls through with fn=2 and produces an AttributeError when decorate() tries to set __cog_concurrent_max__. Could we add a callable(fn) check before this return and raise a clearer TypeError, something like concurrent must be used as @concurrent or @concurrent(max=...)?
There was a problem hiding this comment.
Fixed in cf33465: added a `callable(fn)` guard that raises `TypeError("concurrent must be used as @Concurrent or @Concurrent(max=...)")` before the `decorate(fn)` return, so positional usage like `@concurrent(2)` gives a clear error instead of an `AttributeError`. Added `test_concurrent_rejects_positional_argument`.
|
|
||
| let pred = PythonPredictor::load(py, &self.predictor_ref) | ||
| .map_err(|e| SetupError::load(e.to_string()))?; | ||
| if self.max_concurrency > 1 && !pred.is_async() { |
There was a problem hiding this comment.
This runtime guard is the last line of defense for operator/env overrides, but I don't see a direct test that sets COG_MAX_CONCURRENCY=2 with a sync predictor and asserts setup fails with this message. The parser/build tests cover related paths, but a small coglet-python or integration test for this worker setup failure would make the runtime contract less fragile.
There was a problem hiding this comment.
Added in cf33465: a `tests` module in `worker_bridge.rs` with `setup_rejects_sync_predictor_with_concurrency` (creates a handler with `max_concurrency=2` + a sync predictor, calls `setup().await`, and asserts it fails with the guard message) plus `setup_allows_async_predictor_with_concurrency` for the passing case.
| } | ||
|
|
||
| func functionIsAsync(node *sitter.Node, source []byte) bool { | ||
| return strings.HasPrefix(strings.TrimSpace(Content(node, source)), "async def ") |
There was a problem hiding this comment.
This async detection is a bit brittle because it depends on the exact text prefix async def . Valid Python with different whitespace, like async def, would be parsed as non-async and then fail @concurrent(max > 1). Could we use the tree-sitter structure/token instead, or at least a regexp like ^async\s+def\s+?
There was a problem hiding this comment.
Fixed in cf33465: `functionIsAsync` now uses the tree-sitter parse tree (`UnwrapFunction` + scanning for the anonymous `async` token) instead of the `"async def "` text prefix. Verified this handles whitespace variants like `async def`.
- build: make predictor metadata parse non-fatal on schema opt-out paths - build: marshal effective config in image label to avoid concurrency drift - schema: detect async via tree-sitter structure instead of text prefix - sdk: raise clear TypeError on positional @Concurrent usage - coglet-python: add worker setup guard tests for COG_MAX_CONCURRENCY - docs: clarify @cog.concurrent version and yaml precedence; regen llms.txt
|
LGTM |
Summary
Example
Testing
Fixes #2811