Bugfixes, add INFO command, graceful shutdown. elimitate dead code

- Remove unsed handleClientKill, handleClientKill
- Fix misleading error message on rate limit
- Eliminated unused channel-to-pattern index that was causing unnecessary work
- Fixed O(n²) duplicate check in PublishToPattern
- Limit total pattern subscriptions to 10,000
- Limit channel subscriptions per client to 10,000
- Added INFO command
- Added graceful shutdown timeout
- Validate that log directory is writable during config load
- Make SELECT properly handle whitespace around DB index like Redis
- Return proper error instead of empty array for unsupported commands on sharded pubsub stubs
- More accurate error messages throughout
- Updated README.md
This commit is contained in:
eleksir
2026-04-03 10:01:29 +03:00
parent 92cd652b0d
commit 488ce1e4d1
9 changed files with 321 additions and 115 deletions

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"time"
@@ -15,27 +16,29 @@ import (
// Constants for default values - chosen to be production-ready defaults
const (
DefaultBind = "0.0.0.0"
DefaultPort = 6379
DefaultMaxClients = 10000
DefaultReadTimeout = 0 // 0 means no timeout (blocking I/O)
DefaultWriteTimeout = 0 // 0 means no timeout
DefaultMaxMsgBytes = 512 * 1024 * 1024 // 512 MB - allows large messages
DefaultLogLevel = "info"
DefaultLogFile = "" // empty = stdout only
DefaultPidFile = "" // empty = no PID file created
DefaultBind = "0.0.0.0"
DefaultPort = 6379
DefaultMaxClients = 10000
DefaultReadTimeout = 0 // 0 means no timeout (blocking I/O)
DefaultWriteTimeout = 0 // 0 means no timeout
DefaultMaxMsgBytes = 512 * 1024 * 1024 // 512 MB - allows large messages
DefaultLogLevel = "info"
DefaultLogFile = "" // empty = stdout only
DefaultPidFile = "" // empty = no PID file created
DefaultShutdownTimeout = 30 // 30 seconds grace period for shutdown
)
type Config struct {
Bind string `json:"bind"`
Port int `json:"port"`
MaxClients int `json:"max_clients"`
ReadTimeout int `json:"read_timeout"` // in seconds, 0 = no timeout
WriteTimeout int `json:"write_timeout"` // in seconds, 0 = no timeout
MaxMsgBytes int `json:"max_message_bytes"`
LogLevel string `json:"log_level"`
LogFile string `json:"log_file"` // empty = stdout only
PidFile string `json:"pid_file"` // empty = no PID file
Bind string `json:"bind"`
Port int `json:"port"`
MaxClients int `json:"max_clients"`
ReadTimeout int `json:"read_timeout"` // in seconds, 0 = no timeout
WriteTimeout int `json:"write_timeout"` // in seconds, 0 = no timeout
MaxMsgBytes int `json:"max_message_bytes"`
LogLevel string `json:"log_level"`
LogFile string `json:"log_file"` // empty = stdout only
PidFile string `json:"pid_file"` // empty = no PID file
ShutdownTimeout int `json:"shutdown_timeout"` // in seconds, 0 = wait forever (not recommended)
}
// SetDefaults applies sensible defaults when config values are missing or invalid.
@@ -69,6 +72,9 @@ func (c *Config) SetDefaults() {
if c.PidFile == "" {
c.PidFile = DefaultPidFile
}
if c.ShutdownTimeout <= 0 {
c.ShutdownTimeout = DefaultShutdownTimeout
}
}
// Validate checks that all configuration values are within acceptable bounds.
@@ -99,6 +105,37 @@ func (c *Config) Validate() error {
if !validLevels[c.LogLevel] {
return fmt.Errorf("invalid log_level: %s (must be debug, info, warn, or error)", c.LogLevel)
}
// Validate log file directory is writable
if c.LogFile != "" {
dir := filepath.Dir(c.LogFile)
if err := testWriteDir(dir); err != nil {
return fmt.Errorf("log_file: %w", err)
}
}
// Validate shutdown timeout is non-negative
if c.ShutdownTimeout < 0 {
return fmt.Errorf("shutdown_timeout must be non-negative")
}
return nil
}
// testWriteDir checks if a directory is writable by trying to create a temp file
func testWriteDir(dir string) error {
// If directory doesn't exist, check parent
if _, err := os.Stat(dir); os.IsNotExist(err) {
parent := filepath.Dir(dir)
if parent == dir {
return fmt.Errorf("directory does not exist: %s", dir)
}
return testWriteDir(parent)
}
// Directory exists - try to create a temp file
tmp, err := os.CreateTemp(dir, ".pekoe_test_")
if err != nil {
return fmt.Errorf("directory not writable: %s", dir)
}
_ = tmp.Close()
_ = os.Remove(tmp.Name())
return nil
}