Skip to content

Conversation

@elliette
Copy link
Member

@elliette elliette commented Jan 29, 2026

Fixes #9632

DTDManager now subscribes to the Service stream, and broadcasts ServiceRegistered and ServiceUnregistered events to any subscribers.

Currently the EditorClient is the only subscriber, but our AI Assistant will also need to subscribe to those events.

FYI @DanTup I've changed the DTD reconnection logic a little bit. I tested the Flutter panel and Property Editor in VS Code, but you might want to verify as well.

Comment on lines +260 to 263
if (_connection.value case final connection?) {
await connection.close();
}
_connection.value = null;
Copy link
Member Author

Choose a reason for hiding this comment

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

I noticed that we were setting the connection to null without closing it first

_connection.value = connection;
// Close the previous connection.
if (previousConnection != null) {
await previousConnection.close();
Copy link
Member Author

Choose a reason for hiding this comment

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

Before this change, we were closing the connection during the disconnect implementation. I moved it here to the connect implementation instead so that we wait for the new connection before closing the previous connection (this prevents us from dropping any service extension registration events we receive while reconnecting).

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: await previousConnection?.close() would remove the if :)

(is it possible it could be triggered twice? would that cause an error? If so, maybe it should be set to null afterwards too?)

@elliette elliette marked this pull request as ready for review January 30, 2026 23:11
@elliette elliette requested review from a team and kenzieschmoll as code owners January 30, 2026 23:11
@elliette elliette requested review from a team, DanTup, bkonyi and srawlins and removed request for a team, bkonyi and kenzieschmoll January 30, 2026 23:11
expect(await eventQueue1.next, equals(fooBarRegisteredEvent));
expect(await eventQueue2.next, equals(fooBarRegisteredEvent));

await eventQueue1.cancel();
Copy link
Contributor

Choose a reason for hiding this comment

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

In case the above expectations fail, should all of this be in a try/finally, to not hang the process or screw up the following tests?

try {
  expect(...);
} finally {
  await eventQueue1.cancel();
  await eventQueue2.cancel();
}

await manager.connect(fakeDtdUri);

// Subscribe to the service registration stream.
final eventQueue = StreamQueue(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure that this test ensures that invalidEvent is not sent. But I think we can simplify it with the stream matchers in package:test:

expect(manager.serviceRegistrationBroadcastStream,
  emitsInOrder([
    fooBarRegisteredEvent,
    fooBarUnregisteredEvent,
    emitsDone,
  ]),
);

Copy link
Contributor

@DanTup DanTup left a comment

Choose a reason for hiding this comment

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

FYI @DanTup I've changed the DTD reconnection logic a little bit. I tested the Flutter panel and Property Editor in VS Code, but you might want to verify as well.

LGTM - I'll make a note to have a quick test next week too. Thanks!

Comment on lines +35 to +38
/// A stream of [CoreDtdServiceConstants.serviceRegisteredKind] and
/// [CoreDtdServiceConstants.serviceUnregisteredKind] events.
///
/// Since this is a broadcast stream, it supports multiple subscribers.
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps it's worth a note/hint on this comment suggesting that if listening to this, callers likely also want to call getRegisteredServices after subscribing to ensure any already-registered services are considered. It might be easy to forget that (given the original API was a single-listener stream that would've buffered) and introduce races.

_connection.value = connection;
// Close the previous connection.
if (previousConnection != null) {
await previousConnection.close();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: await previousConnection?.close() would remove the if :)

(is it possible it could be triggered twice? would that cause an error? If so, maybe it should be set to null afterwards too?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[AI Assistance] Shared DTD service extension manager

3 participants