Skip to content

feat(provider): get-service-config provider request - #14175

Open
ndeloof wants to merge 1 commit into
docker:mainfrom
ndeloof:provider-control-channel
Open

feat(provider): get-service-config provider request#14175
ndeloof wants to merge 1 commit into
docker:mainfrom
ndeloof:provider-control-channel

Conversation

@ndeloof

@ndeloof ndeloof commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Providers could not see the definition of the service they manage: options had to be duplicated between the compose file and the provider, or re-resolved outside the running process.

A provider may now emit {"type": "get-service-config"} on stdout; compose answers on the provider's stdin with one JSON line holding the resolved canonical configuration of the provider's own service, from the in-memory model. A compose that predates the message aborts on it and never writes to stdin, so a provider treats EOF as "unsupported, upgrade compose".

Scoped down from the original version: addhost and the links-style variables convention are superseded by the relay approach (#14193).

🤖 Generated with Claude Code

@docker-agent docker-agent left a comment

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.

Assessment: 🟢 APPROVE

The control-channel implementation is well-structured. The synchronous request/response loop is simple and correct for the typical case. One finding in the example code, and one lower-confidence note below.

Lower-confidence findings (not posted inline)

  • [low] pkg/compose/plugins.go:208 — Potential deadlock when large service config response exceeds OS pipe buffer (confidence: weak 52/100). The synchronous responses.Encode() write to the provider's stdin happens in the same goroutine that reads from stdout. If the marshalled types.ServiceConfig ever exceeds the OS pipe buffer (~64 KB on Linux), and the provider is blocked waiting for this response before writing more output, neither end makes progress. Typical configs are well under 1 KB so this is unlikely in practice, but a goroutine for the write would eliminate the risk entirely.

Comment thread docs/examples/provider.go Outdated
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/compose/plugins.go 66.66% 6 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@ndeloof
ndeloof force-pushed the provider-control-channel branch from cc1654d to a6f92eb Compare September 3, 2026 17:02
@ndeloof ndeloof changed the title feat(provider): stdio control channel serving the resolved service config feat(provider): get-service-config message serving the resolved service config Sep 3, 2026
@ndeloof
ndeloof force-pushed the provider-control-channel branch 2 times, most recently from 279831f to 3b98f63 Compare September 4, 2026 10:06
@ndeloof ndeloof changed the title feat(provider): get-service-config message serving the resolved service config feat(provider): get-service-config, addhost, and endpoint conventions Sep 4, 2026
@ndeloof
ndeloof force-pushed the provider-control-channel branch from 3b98f63 to 9250d4a Compare September 7, 2026 07:54
@ndeloof ndeloof changed the title feat(provider): get-service-config, addhost, and endpoint conventions feat(provider): get-service-config provider request Sep 7, 2026
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

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.

Assessment: 🟡 NEEDS ATTENTION

One medium-severity correctness issue found in the example code added by this PR.

Comment thread docs/examples/provider.go Outdated
@ndeloof
ndeloof force-pushed the provider-control-channel branch from 9250d4a to 500f4e0 Compare September 7, 2026 08:03
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

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.

Assessment: 🟡 NEEDS ATTENTION

Two findings in the newly introduced get-service-config protocol code:

  1. docs/examples/provider.go:108 — error path emits hand-rolled JSON that breaks on special characters in the error string, contradicting the comment on the very next line.
  2. pkg/compose/plugins.go:183 — synchronous write to the provider's stdin pipe inside the stdout-reading loop can deadlock when the serialized ServiceConfig JSON exceeds the OS pipe buffer (~64 KB).

Comment thread docs/examples/provider.go Outdated
Comment thread pkg/compose/plugins.go Outdated

@glours glours left a comment

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: this hand-rolls what json.Decoder already does — bufio.Reader.ReadBytes('\n') + json.Unmarshal assumes each message is exactly one newline-terminated line, which happens to hold today but is a more fragile assumption than just decoding from the stream directly.
docs/examples/provider.go, added by this same PR, already uses the simpler json.NewDecoder(os.Stdin) / .Decode(&config) pattern, worth mirroring it here for consistency (and it lets you drop the bufio import too).

Comment thread pkg/compose/plugins_control_test.go Outdated
Comment thread pkg/compose/plugins_control_test.go Outdated
Comment thread pkg/compose/plugins_control_test.go Outdated
Comment thread pkg/compose/plugins_control_test.go Outdated
@ndeloof
ndeloof force-pushed the provider-control-channel branch from 500f4e0 to 1dcf08b Compare September 7, 2026 09:27
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@ndeloof
ndeloof requested a review from docker-agent September 7, 2026 09:33

@docker-agent docker-agent left a comment

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.

Assessment: 🟡 NEEDS ATTENTION

Two medium/low-confidence correctness findings in the new get-service-config control-channel handler. The core issue is that goroutines dispatched to write the config reply to stdin are not tracked by a sync.WaitGroup, so they can write to a closed pipe when the read loop exits — silently dropping the reply to the provider.

Comment thread pkg/compose/plugins.go
Comment thread pkg/compose/plugins.go
@ndeloof
ndeloof marked this pull request as ready for review September 7, 2026 09:35
@ndeloof
ndeloof requested review from a team as code owners September 7, 2026 09:35
@ndeloof
ndeloof requested a review from glours September 7, 2026 09:35
@ndeloof
ndeloof force-pushed the provider-control-channel branch from 1dcf08b to e1ce900 Compare September 7, 2026 09:38
@ndeloof
ndeloof requested a review from docker-agent September 7, 2026 09:38

@docker-agent docker-agent left a comment

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.

Assessment: 🟡 NEEDS ATTENTION

One medium-severity finding introduced by this PR.

Comment thread pkg/compose/plugins.go

@docker-agent docker-agent left a comment

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.

Assessment: 🟡 NEEDS ATTENTION

One CONFIRMED medium-severity race condition found in the new get-service-config control channel implementation.

Comment thread pkg/compose/plugins.go

@docker-agent docker-agent left a comment

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.

Assessment: 🟢 APPROVE

The get-service-config protocol implementation is well-structured. The concurrency design in plugins.go correctly handles the bidirectional stdin/stdout channel:

  • Goroutine coordination: answers.WaitGroup + stdinMu mutex properly serialize writes to the provider's stdin pipe, preventing interleaving and buffering deadlocks.
  • Defer ordering: The two-branch defer (close-then-wait on error paths, wait-then-close on clean exit) is the right design — it prevents hanging when the provider is still alive and reading, while ensuring in-flight writes complete on normal exit.
  • Closure safety: payload is declared locally in the GetServiceConfigType case block before answers.Add(1), so each goroutine captures its own copy — no shared-variable capture issue.
  • processExited access: Read and written exclusively in the main goroutine (the defer runs in the same goroutine after cmd.Wait()) — no data race.
  • Decoder reuse: The example provider correctly creates json.NewDecoder(os.Stdin) once and reuses it across requests, avoiding the read-ahead pitfall.
  • JSON safety: Config values written to setenv messages go through json.Marshal rather than string interpolation — no injection risk.

No bugs introduced by this PR.

Providers could not see the definition of the service they manage:
options had to be duplicated between the compose file and the provider,
or the provider had to re-resolve the model on its own.

A provider may now emit {"type": "get-service-config"} on stdout;
compose answers on the provider's stdin with one JSON line holding the
resolved canonical configuration of the provider's own service,
straight from the in-memory model. The message can be repeated; each
occurrence is answered with one line. Detection is by construction: a
compose that predates the message aborts on it and never writes to
stdin, so the provider treats EOF as 'unsupported, upgrade compose'.

The example provider demonstrates the round trip, backed by an e2e
scenario; unit tests drive executePlugin against a helper-process
provider and cover the injection.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof force-pushed the provider-control-channel branch from e1ce900 to 6a4b684 Compare September 7, 2026 09:44
@ndeloof
ndeloof requested a review from docker-agent September 7, 2026 09:44

@docker-agent docker-agent left a comment

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.

Assessment: 🟢 APPROVE

The get-service-config control-channel implementation is well-structured. The goroutine-based stdin write pattern correctly avoids deadlocks (large configs won't block the stdout read loop), the LIFO defer ordering is sound for both normal and error exit paths, and the processExited flag correctly gates the two cleanup strategies. The omitempty addition on JsonMessage.Message is safe: Compose only decodes JsonMessage (reads provider stdout) and never encodes it, so omitting the empty field from the test helper's output has no impact on real providers. The provider example correctly reuses a single json.Decoder for stdin across multiple requests, avoiding read-ahead data loss.

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.

3 participants