Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,71 @@ Additionally, you can manage heartbeat logic within the (Codec)OnCron method in

If you're using WebSocket, you don't need to worry about heartbeat request/response, as Getty handles this task within session.go's (Session)handleLoop method by sending and receiving WebSocket ping/pong frames. Your responsibility is to check whether the WebSocket session has timed out or not within codec.go's (Codec)OnCron method using session.go's (Session)GetActive method.

For code examples, you can refer to https://github.com/AlexStocks/getty-examples.
For code examples, you can refer to [getty-examples](https://github.com/AlexStocks/getty-examples).

## Callback System

Getty provides a robust callback system that allows you to register and manage callback functions for session lifecycle events. This is particularly useful for cleanup operations, resource management, and custom event handling.

### Key Features

- **Thread-safe operations**: All callback operations are protected by mutex locks
- **Replace semantics**: Adding a callback with the same handler and key will replace the existing one
- **Panic safety**: Callback panics are properly handled and logged with stack traces
- **Ordered execution**: Callbacks are executed in the order they were added

Comment on lines 23 to 33
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Doc vs code: Panic safety statement conflicts with implementation/tests.

Callbacks currently propagate panics; README says they’re recovered and logged. Align with the chosen behavior. I recommend adding recover in callbacks.Invoke (see suggested patch) and updating tests accordingly.

🤖 Prompt for AI Agents
In README.md around lines 23 to 33, the docs claim "Panic safety: Callback
panics are properly handled and logged with stack traces" but the code/tests
currently let panics propagate; to align behavior implement panic recovery
inside the callback invocation path (callbacks.Invoke) by wrapping each handler
call with a defer that recovers, logs the panic and stack trace, and prevents
propagation, while preserving ordered execution and mutex semantics; after
adding recover, update the tests to assert that panics do not escape Invoke and
that a log entry containing the panic and stack trace was produced (or
alternatively update the README to accurately reflect current behavior if you
prefer making docs change instead of code).

### Usage Example

```go
// Add a close callback
session.AddCloseCallback("cleanup", "resources", func() {
// Cleanup resources when session closes
cleanupResources()
})

// Remove a specific callback
session.RemoveCloseCallback("cleanup", "resources")

// Callbacks are automatically executed when the session closes
```

**Note**: Callbacks should be fast/non-blocking; move heavy work to separate goroutines to avoid delaying shutdown.

### Callback Management

- **AddCloseCallback**: Register a callback to be executed when the session closes
- **RemoveCloseCallback**: Remove a previously registered callback
- **Thread Safety**: All operations are thread-safe and can be called concurrently

Comment on lines 52 to 57
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Document comparable requirement for (handler, key).

Add a note: “handler and key must be comparable (e.g., string/int/pointer). Passing maps/slices will cause a runtime error; convert to a stable identifier first.”

🤖 Prompt for AI Agents
In README.md around lines 49 to 54, add a note to the Callback Management
section clarifying that the handler and key parameters must be comparable types
(for example string, int, or pointer) and that passing non-comparable types such
as maps or slices will cause a runtime error; instruct users to convert
non-comparable values to a stable identifier (e.g., serialized string, integer
ID, or pointer) before registering callbacks. Ensure the note is concise,
mentions examples, and is placed under the AddCloseCallback/RemoveCloseCallback
bullets so users see the requirement when using those APIs.

### Type Requirements

The `handler` and `key` parameters must be **comparable types** that support the `==` operator:

**✅ Supported types:**
- **Basic types**: `string`, `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `uintptr`, `float32`, `float64`, `bool`, `complex64`, `complex128`
- **Pointer types**: Pointers to any type (e.g., `*int`, `*string`, `*MyStruct`)
- **Interface types**: Interface types (compared by type and value)
- **Channel types**: Channel types (compared by channel identity)
- **Array types**: Arrays of comparable elements (e.g., `[3]int`, `[2]string`)
- **Struct types**: Structs where all fields are comparable types

**❌ Not supported (will cause compile errors):**
- `map` types (e.g., `map[string]int`)
- `slice` types (e.g., `[]int`, `[]string`)
- `func` types (e.g., `func()`, `func(int) string`)
- Structs containing non-comparable fields (maps, slices, functions)

**Examples:**
```go
// ✅ Valid usage
session.AddCloseCallback("user", "cleanup", callback)
session.AddCloseCallback(123, "cleanup", callback)
session.AddCloseCallback(true, false, callback)

// ❌ Invalid usage (compile error)
session.AddCloseCallback(map[string]int{"a": 1}, "key", callback)
session.AddCloseCallback([]int{1, 2, 3}, "key", callback)
```

## About network transmission in getty

Expand Down
66 changes: 65 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,71 @@ Getty 是一个使用 Golang 开发的异步网络 I/O 库。它适用于 TCP、

如果您使用 WebSocket,您无需担心心跳请求/响应,因为 Getty 在 session.go 的 (Session)handleLoop 方法内通过发送和接收 WebSocket ping/pong 帧来处理此任务。您只需在 codec.go 的 (Codec)OnCron 方法内使用 session.go 的 (Session)GetActive 方法检查 WebSocket 会话是否已超时。

有关代码示例,请参阅 https://github.com/AlexStocks/getty-examples。
有关代码示例,请参阅 [AlexStocks/getty-examples](https://github.com/AlexStocks/getty-examples)。

## 回调系统

Getty 提供了一个强大的回调系统,允许您为会话生命周期事件注册和管理回调函数。这对于清理操作、资源管理和自定义事件处理特别有用。

### 主要特性

- **线程安全操作**:所有回调操作都受到互斥锁保护
- **替换语义**:使用相同的处理器和键添加回调将替换现有的回调
- **Panic 安全性**:回调中的 panic 会被正确处理并记录堆栈跟踪
- **有序执行**:回调按照添加的顺序执行

### 使用示例

```go
// 添加关闭回调
session.AddCloseCallback("cleanup", "resources", func() {
// 当会话关闭时清理资源
cleanupResources()
})

// 移除特定回调
session.RemoveCloseCallback("cleanup", "resources")

// 当会话关闭时,回调会自动执行
```

**注意**:关闭回调应尽量快速、避免阻塞或长耗时操作,否则可能延长会话关闭耗时;重活移交到独立 goroutine。

### 回调管理

- **AddCloseCallback**:注册一个在会话关闭时执行的回调
- **RemoveCloseCallback**:移除之前注册的回调
- **线程安全**:所有操作都是线程安全的,可以并发调用

### 类型要求

`handler` 和 `key` 参数必须是**可比较的类型**,支持 `==` 操作符:

**✅ 支持的类型:**
- **基本类型**:`string`、`int`、`int8`、`int16`、`int32`、`int64`、`uint`、`uint8`、`uint16`、`uint32`、`uint64`、`uintptr`、`float32`、`float64`、`bool`、`complex64`、`complex128`
- **指针类型**:指向任何类型的指针(如 `*int`、`*string`、`*MyStruct`)
- **接口类型**:接口类型(按类型和值比较)
- **通道类型**:通道类型(按通道标识比较)
- **数组类型**:可比较元素的数组(如 `[3]int`、`[2]string`)
- **结构体类型**:所有字段都是可比较类型的结构体

**❌ 不支持的类型(会导致编译错误):**
- `map` 类型(如 `map[string]int`)
- `slice` 类型(如 `[]int`、`[]string`)
- `func` 类型(如 `func()`、`func(int) string`)
- 包含不可比较字段的结构体(maps、slices、functions)

**示例:**
```go
// ✅ 有效用法
session.AddCloseCallback("user", "cleanup", callback)
session.AddCloseCallback(123, "cleanup", callback)
session.AddCloseCallback(true, false, callback)

// ❌ 无效用法(编译错误)
session.AddCloseCallback(map[string]int{"a": 1}, "key", callback)
session.AddCloseCallback([]int{1, 2, 3}, "key", callback)
```

## 关于 Getty 中的网络传输

Expand Down
129 changes: 129 additions & 0 deletions transport/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package getty

// callbackNode represents a node in the callback linked list
// Each node contains handler identifier, key, callback function and pointer to next node
type callbackNode struct {
handler any // Handler identifier, used to identify the source or type of callback
key any // Unique identifier key for callback, used in combination with handler
call func() // Actual callback function to be executed
next *callbackNode // Pointer to next node, forming linked list structure
}

// callbacks is a singly linked list structure for managing multiple callback functions
// Supports dynamic addition, removal and execution of callbacks
type callbacks struct {
first *callbackNode // Pointer to the first node of the linked list
last *callbackNode // Pointer to the last node of the linked list, used for quick addition of new nodes
}

// Add adds a new callback function to the callback linked list
// Parameters:
// - handler: Handler identifier, can be any type
// - key: Unique identifier key for callback, used in combination with handler
// - callback: Callback function to be executed, ignored if nil
//
// Note: If a callback with the same handler and key already exists, it will be replaced
func (t *callbacks) Add(handler, key any, callback func()) {
// Prevent adding empty callback function
if callback == nil {
return
}

// Check if a callback with the same handler and key already exists
for cb := t.first; cb != nil; cb = cb.next {
if cb.handler == handler && cb.key == key {
// Replace existing callback
cb.call = callback
return
}
}

// Create new callback node
newItem := &callbackNode{handler, key, callback, nil}

if t.first == nil {
// If linked list is empty, new node becomes the first node
t.first = newItem
} else {
// Otherwise add new node to the end of linked list
t.last.next = newItem
}
// Update pointer to last node
t.last = newItem
}

// Remove removes the specified callback function from the callback linked list
// Parameters:
// - handler: Handler identifier of the callback to be removed
// - key: Unique identifier key of the callback to be removed
//
// Note: If no matching callback is found, this method has no effect
func (t *callbacks) Remove(handler, key any) {
var prev *callbackNode

// Traverse linked list to find the node to be removed
for callback := t.first; callback != nil; prev, callback = callback, callback.next {
// Found matching node
if callback.handler == handler && callback.key == key {
if t.first == callback {
// If it's the first node, update first pointer
t.first = callback.next
} else if prev != nil {
// If it's a middle node, update the next pointer of the previous node
prev.next = callback.next
}

if t.last == callback {
// If it's the last node, update last pointer
t.last = prev
}

// Return immediately after finding and removing
return
}
}
}

// Invoke executes all registered callback functions in the linked list
// Executes each callback in the order they were added
// Note: If a callback function is nil, it will be skipped
// If a callback panics, it will be handled by the outer caller's panic recovery
func (t *callbacks) Invoke() {
// Traverse the entire linked list starting from the head node
for callback := t.first; callback != nil; callback = callback.next {
// Ensure callback function is not nil before executing
if callback.call != nil {
callback.call()
}
}
}

// Len returns the number of callback functions in the linked list
// Return value: Total number of currently registered callback functions
func (t *callbacks) Len() int {
var count int

// Traverse linked list to count
for callback := t.first; callback != nil; callback = callback.next {
count++
}

return count
}
Loading
Loading