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
Requestdataclass, mapping RPC options likewait_for_finishandreturn_command_outputinto 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
\nbefore parsing the JSON. - It implements an exponential backoff sleep (starting at
0.0005seconds 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
OSErrorwith error code32(sharing violation / file in use), the file is renamed and moved to agraveyardsubdirectory using a random UUID. This clears the communication path immediately so subsequent RPC requests can proceed without blocking on lock releases.