Skip to content

rpc_client

This directory implements a robust, file-based Remote Procedure Call (RPC) client used by Talon to communicate with external applications (such as editors hosting a command server).

By leveraging a shared directory in the system's temporary folder, the client serializes command requests to disk, triggers execution in the active application, and waits for a structured JSON response.

Core Architecture

The RPC client works through a coordinated workflow of request writing, execution triggering, and response polling.

[ Talon Action ]
       │
       ▼
 1. Write Request ──► [ write_request.py ] ──► (request.json)
       │
       ▼
 2. Trigger Exec  ──► (Sends keypress to Application)
       │                                            │
       ▼                                            ▼
 3. Poll Response ◄── [ read_json_with_timeout.py ] ◄── Writes (response.json)
       │
       ▼
 4. Cleanup       ──► [ robust_unlink.py ] ──► (Deletes JSON files)

1. Request Orchestration

The entry point for this RPC mechanism is rpc_client.py. It registers a Talon action user.rpc_client_run_command, which drives the entire lifecycle of a command execution:

  • Directory Discovery: It resolves the communication path using get_communication_dir_path.py, which targets the system temporary directory. On Unix-like systems, it appends the current user ID to the directory name to prevent multi-user permission conflicts, while on Windows it relies on user-specific temporary paths.
  • Request Packaging: It builds a unique request structure containing a command ID, arguments, execution flags, and a newly generated UUID to prevent replay or mismatched response issues.
  • Execution Trigger: It writes the command out and executes a callback function (typically triggering a keypress in the target application) to prompt the application's command server to process the written request.
  • Validation: Once the response is read, it verifies the matching UUID, logs warnings, raises exceptions on errors, and yields the final command result.

2. Request Serialization & Safety

Data models and write locks are governed by types.py and write_request.py:

  • types.py defines the Request dataclass, mapping RPC options like wait_for_finish and return_command_output into a serializable dictionary.
  • write_request.py implements write-exclusive safety. By opening the target file in exclusive creation mode ("x"), it prevents concurrent Talon processes from overwriting an active RPC request. If a file already exists, it checks the file's modification age. If it exceeds a 60-second threshold (STALE_TIMEOUT_MS), it declares it stale and safely overwrites it.

3. Response Polling

Because the communication is asynchronous, the client must safely wait for the external application to produce a result. This is handled by read_json_with_timeout.py:

  • It repeatedly polls the filesystem for the response file.
  • To avoid reading partially written files, it guarantees that the file contents end with a newline \n before parsing the JSON.
  • It implements an exponential backoff sleep (starting at 0.0005 seconds and scaling up to remaining timeout time) to avoid high CPU utilization during busy-waiting, while still keeping latency low.
  • If no response is fully written within 3 seconds, it throws a timeout exception.

4. Robust File Cleanup

File locks are highly prone to OS-specific issues—especially on Windows, where open files cannot be unlinked. robust_unlink.py provides an OS-safe cleanup routine:

  • It attempts a standard file unlinking (Path.unlink).
  • If Windows raises an OSError with error code 32 (sharing violation / file in use), the file is renamed and moved to a graveyard subdirectory using a random UUID. This clears the communication path immediately so subsequent RPC requests can proceed without blocking on lock releases.