mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 09:14:38 +08:00
Refresh client ownership developer docs
This commit is contained in:
parent
cbaf02c769
commit
2b43ce6a3e
3 changed files with 48 additions and 26 deletions
|
|
@ -83,6 +83,9 @@
|
||||||
- Refreshed contributor and development guidance so new commands are added
|
- Refreshed contributor and development guidance so new commands are added
|
||||||
through `command_catalog`, `exec_catalog`, and `i18n_text` instead of stale
|
through `command_catalog`, `exec_catalog`, and `i18n_text` instead of stale
|
||||||
`ssh_server.c` / inline-`strcmp` instructions.
|
`ssh_server.c` / inline-`strcmp` instructions.
|
||||||
|
- Refreshed developer ownership guidance to match the current update-sequence
|
||||||
|
model: room broadcasts update shared state only, while each interactive
|
||||||
|
client renders and flushes its own SSH channel.
|
||||||
- `exec_catalog` now owns SSH exec command matching as well as help metadata,
|
- `exec_catalog` now owns SSH exec command matching as well as help metadata,
|
||||||
reducing duplicate command knowledge in `src/exec.c`.
|
reducing duplicate command knowledge in `src/exec.c`.
|
||||||
- Replaced hard-coded `chat.m1ng.space` examples with `chat.example.com` so
|
- Replaced hard-coded `chat.m1ng.space` examples with `chat.example.com` so
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ make release-check # release preflight
|
||||||
make test # unit + integration tests
|
make test # unit + integration tests
|
||||||
make ci-test # local CI-equivalent checks
|
make ci-test # local CI-equivalent checks
|
||||||
make stress-test # concurrent-client stress test
|
make stress-test # concurrent-client stress test
|
||||||
|
make soak-test # idle/reconnect/control-plane soak
|
||||||
|
make slow-client-test # slow interactive-client backpressure
|
||||||
|
make user-lifecycle-test # two-user TUI lifecycle
|
||||||
```
|
```
|
||||||
|
|
||||||
## Debug
|
## Debug
|
||||||
|
|
@ -78,7 +81,8 @@ utf8.c → UTF-8 string handling
|
||||||
## Common Bugs to Avoid
|
## Common Bugs to Avoid
|
||||||
|
|
||||||
1. Don't use `strtok()` on client data - use `strtok_r()` or copy first
|
1. Don't use `strtok()` on client data - use `strtok_r()` or copy first
|
||||||
2. Always increment ref_count before using client outside lock
|
2. Always use `client_addref()` / `client_release()` before using a client
|
||||||
|
outside `g_room->lock`; never modify `ref_count` directly
|
||||||
3. Check SSH API return values (can be SSH_ERROR, SSH_AGAIN, or negative)
|
3. Check SSH API return values (can be SSH_ERROR, SSH_AGAIN, or negative)
|
||||||
4. UTF-8 chars are multi-byte - use utf8_* functions
|
4. UTF-8 chars are multi-byte - use utf8_* functions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,13 @@ TNT uses a multi-threaded architecture with a main accept loop and per-client th
|
||||||
|
|
||||||
### Key Design Principles
|
### Key Design Principles
|
||||||
|
|
||||||
1. **Fixed-size buffers** - No dynamic allocation in hot paths
|
1. **Fixed-size buffers** - Keep message, command, and UI buffers bounded
|
||||||
2. **Reader-writer locks** - Multiple readers, single writer
|
2. **Reader-writer locks** - Multiple readers, single writer for room state
|
||||||
3. **Reference counting** - Prevent use-after-free
|
3. **Per-client output ownership** - Each interactive session writes only to
|
||||||
4. **Ring buffer** - Fixed-size message history (last 100 messages)
|
its own SSH channel
|
||||||
|
4. **Reference counting** - Keep client objects alive across callbacks and
|
||||||
|
cross-thread lookups
|
||||||
|
5. **Ring buffer** - Fixed-size in-memory message history (last 100 messages)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -76,7 +79,7 @@ src/
|
||||||
├── command_catalog.c - COMMAND-mode names, aliases, and help summaries
|
├── command_catalog.c - COMMAND-mode names, aliases, and help summaries
|
||||||
├── exec_catalog.c - SSH exec command matching and help metadata
|
├── exec_catalog.c - SSH exec command matching and help metadata
|
||||||
├── exec.c - SSH exec command dispatch
|
├── exec.c - SSH exec command dispatch
|
||||||
├── chat_room.c - Chat room logic and message broadcasting
|
├── chat_room.c - Chat room state, message ring, and update sequence
|
||||||
├── message.c - Message persistence (RFC3339 format)
|
├── message.c - Message persistence (RFC3339 format)
|
||||||
├── history_view.c - NORMAL-mode scroll window rules
|
├── history_view.c - NORMAL-mode scroll window rules
|
||||||
├── tui.c - Terminal UI rendering (ANSI escape codes)
|
├── tui.c - Terminal UI rendering (ANSI escape codes)
|
||||||
|
|
@ -119,12 +122,15 @@ typedef struct client {
|
||||||
ssh_session session;
|
ssh_session session;
|
||||||
ssh_channel channel;
|
ssh_channel channel;
|
||||||
char username[MAX_USERNAME_LEN];
|
char username[MAX_USERNAME_LEN];
|
||||||
int width, height; // Terminal dimensions
|
_Atomic int width, height; // Terminal dimensions
|
||||||
client_mode_t mode; // INSERT/NORMAL/COMMAND
|
client_mode_t mode; // INSERT/NORMAL/COMMAND
|
||||||
int scroll_pos;
|
int scroll_pos;
|
||||||
bool connected;
|
atomic_bool connected;
|
||||||
|
char *outbox; // Bounded queued interactive output
|
||||||
|
size_t outbox_len, outbox_pos;
|
||||||
int ref_count; // Reference counting
|
int ref_count; // Reference counting
|
||||||
pthread_mutex_t ref_lock;
|
pthread_mutex_t ref_lock;
|
||||||
|
pthread_mutex_t io_lock; // Own SSH channel writes only
|
||||||
} client_t;
|
} client_t;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -134,6 +140,7 @@ typedef struct {
|
||||||
pthread_rwlock_t lock; // Reader-writer lock
|
pthread_rwlock_t lock; // Reader-writer lock
|
||||||
struct client **clients; // Dynamic array
|
struct client **clients; // Dynamic array
|
||||||
int client_count;
|
int client_count;
|
||||||
|
uint64_t update_seq; // Bumped when message history changes
|
||||||
message_t *messages; // Ring buffer
|
message_t *messages; // Ring buffer
|
||||||
int message_count;
|
int message_count;
|
||||||
} chat_room_t;
|
} chat_room_t;
|
||||||
|
|
@ -189,6 +196,9 @@ make anonymous-access-test # Verify default anonymous login behavior
|
||||||
make connection-limit-test # Verify per-IP concurrency and rate limits
|
make connection-limit-test # Verify per-IP concurrency and rate limits
|
||||||
make security-test # Run security feature checks
|
make security-test # Run security feature checks
|
||||||
make stress-test # Run configurable concurrent-client stress test
|
make stress-test # Run configurable concurrent-client stress test
|
||||||
|
make soak-test # Run idle/reconnect/control-plane soak test
|
||||||
|
make slow-client-test # Run slow interactive-client backpressure test
|
||||||
|
make user-lifecycle-test # Run a two-user TUI lifecycle test
|
||||||
make ci-test # Run the same checks as GitHub Actions
|
make ci-test # Run the same checks as GitHub Actions
|
||||||
|
|
||||||
# Individual tests
|
# Individual tests
|
||||||
|
|
@ -197,6 +207,9 @@ cd tests
|
||||||
./test_security_features.sh # Security checks
|
./test_security_features.sh # Security checks
|
||||||
./test_anonymous_access.sh # Anonymous access
|
./test_anonymous_access.sh # Anonymous access
|
||||||
./test_stress.sh # Concurrent connections
|
./test_stress.sh # Concurrent connections
|
||||||
|
./test_soak.sh # Idle/reconnect soak
|
||||||
|
./test_slow_client.sh # Slow-client backpressure
|
||||||
|
./test_user_lifecycle.sh # Two-user TUI lifecycle
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Coverage
|
### Test Coverage
|
||||||
|
|
@ -205,6 +218,10 @@ cd tests
|
||||||
- **Security**: RSA keys, env vars, UTF-8 validation, buffer overflow protection
|
- **Security**: RSA keys, env vars, UTF-8 validation, buffer overflow protection
|
||||||
- **Anonymous**: Passwordless access, any username
|
- **Anonymous**: Passwordless access, any username
|
||||||
- **Stress**: 10 concurrent clients for 30 seconds
|
- **Stress**: 10 concurrent clients for 30 seconds
|
||||||
|
- **Soak**: idle session, reconnect churn, health/stats/users/post/tail
|
||||||
|
- **Slow client**: unread interactive SSH client cannot block control paths
|
||||||
|
- **Lifecycle**: two-user TUI story covering help, history, search, private
|
||||||
|
messages, nickname, action messages, and persistence boundaries
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -244,33 +261,29 @@ while ((!ctx->auth_success || ctx->channel == NULL || !ctx->channel_ready) && !t
|
||||||
|
|
||||||
### 2. Chat Room (chat_room.c)
|
### 2. Chat Room (chat_room.c)
|
||||||
|
|
||||||
**Thread-safe broadcasting:**
|
**Thread-safe message publication:**
|
||||||
```c
|
```c
|
||||||
void room_broadcast(chat_room_t *room, const message_t *msg) {
|
void room_broadcast(chat_room_t *room, const message_t *msg) {
|
||||||
pthread_rwlock_wrlock(&room->lock);
|
pthread_rwlock_wrlock(&room->lock);
|
||||||
|
|
||||||
/* Copy client list with ref counting */
|
room_add_message(room, msg);
|
||||||
client_t **clients_copy = calloc(...);
|
room->update_seq++;
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
clients_copy[i]->ref_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_rwlock_unlock(&room->lock); // Release lock early
|
pthread_rwlock_unlock(&room->lock);
|
||||||
|
|
||||||
/* Render outside lock (avoid deadlock) */
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
tui_render_screen(clients_copy[i]);
|
|
||||||
client_release(clients_copy[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Why this works:**
|
**Why this works:**
|
||||||
- Copy client list while holding write lock
|
- Broadcast updates shared room state only; it does not render or write to
|
||||||
- Increment reference counts
|
any SSH channel.
|
||||||
- Release lock BEFORE rendering
|
- Each interactive session tracks `room_get_update_seq()` in its own
|
||||||
- Render to all clients outside lock
|
`input_run_session()` loop.
|
||||||
- Decrement reference counts (may free clients)
|
- When the sequence changes, the client renders and flushes its own output.
|
||||||
|
- This keeps slow SSH windows local to that client and prevents one recipient
|
||||||
|
from blocking a sender or the whole room.
|
||||||
|
- Cross-client lookups, such as mentions and private messages, must call
|
||||||
|
`client_addref()` before using a client pointer outside `g_room->lock`, then
|
||||||
|
`client_release()` when done. Do not increment `ref_count` directly.
|
||||||
|
|
||||||
### 3. Message Persistence (message.c)
|
### 3. Message Persistence (message.c)
|
||||||
|
|
||||||
|
|
@ -380,6 +393,8 @@ void utf8_remove_last_word(char *str) {
|
||||||
```sh
|
```sh
|
||||||
tests/test_exec_mode.sh # exec command behavior
|
tests/test_exec_mode.sh # exec command behavior
|
||||||
tests/test_interactive_input.sh # COMMAND-mode/TUI behavior
|
tests/test_interactive_input.sh # COMMAND-mode/TUI behavior
|
||||||
|
tests/test_user_lifecycle.sh # end-to-end two-user TUI behavior
|
||||||
|
tests/test_slow_client.sh # slow SSH reader/backpressure behavior
|
||||||
tests/unit/test_i18n.c # localized shared text
|
tests/unit/test_i18n.c # localized shared text
|
||||||
tests/unit/test_command_catalog.c # interactive command metadata
|
tests/unit/test_command_catalog.c # interactive command metadata
|
||||||
tests/unit/test_exec_catalog.c # exec command help metadata
|
tests/unit/test_exec_catalog.c # exec command help metadata
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue