Skip to content

RSDK-10220: Create resource clients on-demand without server check#525

Draft
claude[bot] wants to merge 2 commits intomainfrom
claude/RSDK-10220-create-resource-clients-without-server-check
Draft

RSDK-10220: Create resource clients on-demand without server check#525
claude[bot] wants to merge 2 commits intomainfrom
claude/RSDK-10220-create-resource-clients-without-server-check

Conversation

@claude
Copy link
Copy Markdown

@claude claude bot commented Apr 8, 2026

Summary

  • RobotClient.getResource() now creates resource clients on-demand using the Registry when they are not found in the ResourceManager, instead of throwing ResourceNotFoundException
  • This allows consumers to obtain resource clients whether or not the resource currently exists on the server — errors surface only when the client actually communicates with the resource
  • Addresses caching issues where a resource may be ready but not yet refreshed, or broken by a reconfigure

DRI

@allisonschiang is the responsible engineer for this PR.

Test plan

  • Added unit test: creates resource client on-demand for registered subtype
  • Added unit test: returns cached resource on subsequent calls
  • Added unit test: throws for unregistered subtype
  • Added unit test: on-demand client works end-to-end with real gRPC server
  • Added unit test: on-demand client for resource not on server fails at call time, not creation time
  • All 593 existing + new tests pass
  • make format, make analyze, make test all pass

Jira: RSDK-10220

🤖 Generated with Claude Code

RobotClient.getResource() now creates resource clients on-demand using
the Registry when they are not found in the ResourceManager, instead of
throwing ResourceNotFoundException. This avoids blocking client creation
on server availability, offloading error handling to the consumer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude claude bot requested a review from a team as a code owner April 8, 2026 21:44
@claude claude bot requested review from allisonschiang and stuqdog April 8, 2026 21:44
@allisonschiang allisonschiang marked this pull request as draft April 8, 2026 21:50
- Remove unused `import 'package:viam_sdk/src/components/sensor/sensor.dart'`
- Replace `expect(() => client.position(), throwsA(anything))` with
  `await expectLater(client.position(), throwsA(anything))` to properly
  await the async gRPC failure in the last test case

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread lib/src/robot/client.dart
return _manager.getResource<T>(name);
try {
return _manager.getResource<T>(name);
} on ResourceNotFoundException {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

lookupSubtype throws a generic Exception (message: "Subtype not registered in registry") when the subtype is unknown — not a ResourceNotFoundException. This changes the error contract: callers who catch ResourceNotFoundException to handle "resource not available" will miss the case where the subtype is unregistered. Consider re-throwing as ResourceNotFoundException, or documenting clearly that an unregistered subtype propagates a raw Exception.

Comment thread lib/src/robot/client.dart
final registration = Registry.instance.lookupSubtype(Subtype.fromResourceName(name));
final resource = registration.rpcClientCreator(name.name, _channel);
_manager.register(name, resource);
return resource as T;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

resource as T is an unchecked cast at runtime. If the caller passes a mismatched type (e.g. getResource<Camera>(motorName)), this throws a TypeError here rather than at _manager.getResource<T>. This is consistent with the manager's own cast, but worth noting: there is no guard if T does not extend Resource. This is the same exposure that already existed in the manager path, so no change required — just confirming it is intentional.

Comment thread lib/src/robot/client.dart
return _manager.getResource<T>(name);
} on ResourceNotFoundException {
final registration = Registry.instance.lookupSubtype(Subtype.fromResourceName(name));
final resource = registration.rpcClientCreator(name.name, _channel);
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

After a reconnect, refresh() replaces _manager with a new ResourceManager built only from server-returned resources. On-demand resources added to the old _manager by this path are not carried over to the new manager, and _resetResourceChannel is never called for them (it only iterates responseNames). Any client reference the caller is holding after a reconnect will have a stale channel and will silently fail until they call getResource again (which re-creates on-demand). This is an inconsistency vs. server-known resources, which do get their channels reset. At minimum the docstring should note this, and the reconnect test in the test plan should be exercised.

// used by RobotClient.getResource(). Since RobotClient requires a
// live connection, we test the same code path through its components:
// ResourceManager + Registry.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The test group title says "RobotClient getResource on-demand creation" but none of the tests call RobotClient.getResource() — they reproduce its internal logic inline via ResourceManager + Registry. The actual method under test is never exercised. This means a regression in how RobotClient.getResource assembles those calls (e.g. wrong field used as the name, wrong channel passed) would not be caught. Ideally these tests should be integration-style tests that call robot.getResource<T>(name) on a real or fake RobotClient.

Copy link
Copy Markdown
Author

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Code Review — RSDK-10220

lib/src/gen/ was not modified. ✓

Fixes committed (399749a)

  • Unused import removedimport 'package:viam_sdk/src/components/sensor/sensor.dart' was dead code in the test file.
  • Async assertion fixedexpect(() => client.position(), throwsA(anything)) was replaced with await expectLater(client.position(), throwsA(anything)). The original form does not properly await the Future returned by the gRPC call; the test could pass vacuously before the async failure was observed.

Design concerns (inline comments)

  1. Changed error contract for unregistered subtypes (client.dart:267) — When the subtype is unknown, Registry.lookupSubtype throws a raw Exception, not ResourceNotFoundException. Callers that were catching ResourceNotFoundException to handle "resource not found" will silently miss the unregistered-subtype case. Should either re-throw as ResourceNotFoundException or be explicitly documented.

  2. On-demand resources lose their channel after reconnect (client.dart:269) — refresh() replaces _manager wholesale from server-returned resources; on-demand entries are dropped and _resetResourceChannel is never called for them. After a reconnect, any caller holding a reference to an on-demand client will have a stale channel reference until they call getResource again. Server-known resources are handled consistently via _resetResourceChannel; on-demand ones are not. At minimum this should be documented in the docstring.

  3. Tests bypass the actual method (client_test.dart:21) — All five test cases reproduce the internals of RobotClient.getResource() inline rather than calling the method itself. A copy-paste error in the implementation (e.g. using the wrong field for the resource name) would go undetected. Tests should exercise robot.getResource<T>(name) directly.

  4. resource as T unchecked cast (client.dart:271) — Consistent with how ResourceManager.getResource already behaves; confirmed intentional.

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.

0 participants