### Run Redis-Compatible Server in Go Source: https://context7.com/coocood/freecache/llms.txt Initializes and starts a toy Redis-compatible server using FreeCache. This server supports basic Redis commands like GET, SET, SETEX, DEL, PING, and DBSIZE, along with pipelining. It requires specifying the cache size in bytes and the port to listen on. ```go // server/main.go - Running the Redis-compatible server package main import ( "runtime" "runtime/debug" "github.com/coocood/freecache" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU() - 1) // Create 256MB cache server := NewServer(256 * 1024 * 1024) debug.SetGCPercent(10) // Start server on port 7788 server.Start(":7788") } // Connect using redis-cli or any Redis client: // $ redis-cli -p 7788 // 127.0.0.1:7788> SET mykey "Hello" // OK // 127.0.0.1:7788> GET mykey // "Hello" // 127.0.0.1:7788> SETEX tempkey 60 "expires in 60s" // OK // 127.0.0.1:7788> DEL mykey // (integer) 1 // 127.0.0.1:7788> DBSIZE // (integer) 1 // 127.0.0.1:7788> PING // PONG ``` -------------------------------- ### Freecache Server Start Method (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html Starts the Freecache server, listening for incoming TCP connections on the specified address. It handles client connections by spawning read and write loops for each session. ```Go func (server *Server) Start(addr string) error { l, err := net.Listen("tcp", addr) if err != nil { log.Println(err) return err } defer l.Close() log.Println("Listening on port", addr) for { tcpListener := l.(*net.TCPListener) tcpListener.SetDeadline(time.Now().Add(time.Second)) conn, err := l.Accept() if err != nil { if ne, ok := err.(net.Error); ok && ne.Temporary() { continue } return err } session := new(Session) session.conn = conn session.replyChan = make(chan *bytes.Buffer, 100) session.addr = conn.RemoteAddr().String() session.server = server session.reader = bufio.NewReader(conn) go session.readLoop() go session.writeLoop() } } ``` -------------------------------- ### Server Main Function Source: https://github.com/coocood/freecache/wiki/coverage.html The main function sets up the server, configures garbage collection, starts a debug HTTP server, and then starts the main Freecache server. ```APIDOC ## Server Main Function ### main Initializes the server, sets the number of goroutines to use, configures garbage collection, starts a debug HTTP server on port 6060, and then starts the main Freecache server on port 7788. ```go func main() ``` ``` -------------------------------- ### Get Value by Key from Cache in Go Source: https://context7.com/coocood/freecache/llms.txt Provides an example of retrieving data from FreeCache using the `Get` method. It demonstrates handling both successful retrievals and cases where the key is not found or has expired, returning `ErrNotFound`. ```Go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) cache.Set([]byte("session:abc123"), []byte("user_data_here"), 3600) // Retrieve existing key value, err := cache.Get([]byte("session:abc123")) if err != nil { fmt.Printf("Get error: %v\n", err) } else { fmt.Printf("Session data: %s\n", value) } // Attempt to retrieve non-existent key _, err = cache.Get([]byte("session:nonexistent")) if err == freecache.ErrNotFound { fmt.Println("Key not found in cache") } // Output: // Session data: user_data_here // Key not found in cache } ``` -------------------------------- ### Process Cache Commands in a Session Loop (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html This function runs in a loop, continuously reading client requests and processing them. It dispatches commands like SET, SETEX, GET, DEL, PING, and DBSIZE to the underlying cache implementation. It handles different argument counts and command types, formatting responses accordingly, including errors and bulk data. Dependencies include bytes.Buffer, strconv, and the cache interface. ```Go func (down *Session) readLoop() { var req = new(Request) req.buf = new(bytes.Buffer) for { req.Reset() err := down.server.ReadClient(down.reader, req) if err != nil { close(down.replyChan) return } reply := new(bytes.Buffer) if len(req.args) == 4 && bytes.Equal(req.args[0], SETEX) { expire, err := btoi(req.args[2]) if err != nil { reply.Write(ERROR_UNSUPPORTED) } else { down.server.cache.Set(req.args[1], req.args[3], expire) reply.Write(OK) } } else if len(req.args) == 3 && bytes.Equal(req.args[0], SET) { down.server.cache.Set(req.args[1], req.args[2], 0) reply.Write(OK) } else if len(req.args) == 2 { if bytes.Equal(req.args[0], GET) { value, err := down.server.cache.Get(req.args[1]) if err != nil { reply.Write(NIL) } else { bukLen := strconv.Itoa(len(value)) reply.Write(BulkSign) reply.WriteString(bukLen) reply.Write(CRLF) reply.Write(value) reply.Write(CRLF) } } else if bytes.Equal(req.args[0], DEL) { if down.server.cache.Del(req.args[1]) { reply.Write(CONE) } else { reply.Write(CZERO) } } } else if len(req.args) == 1 { if bytes.Equal(req.args[0], PING) { reply.Write(PONG) } else if bytes.Equal(req.args[0], DBSIZE) { entryCount := down.server.cache.EntryCount() reply.WriteString(":") reply.WriteString(strconv.Itoa(int(entryCount))) reply.Write(CRLF) } else { reply.Write(ERROR_UNSUPPORTED) } } down.replyChan <- reply } } ``` -------------------------------- ### Get Source: https://context7.com/coocood/freecache/llms.txt Retrieves a value from the cache by key. ```APIDOC ## Get ### Description Retrieves a value from the cache. Returns ErrNotFound if the key is missing or expired. ### Parameters - **key** ([]byte) - Required - The key to retrieve. ### Response #### Success Response (200) - **value** ([]byte) - The retrieved data. #### Error Response - **ErrNotFound** - Returned when the key does not exist. ``` -------------------------------- ### Lookup Entry by Hash and Key in Segment Slot (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html The 'lookup' function searches within a specific slot for an entry matching the provided 16-bit hash and key. It uses 'entryPtrIdx' to find a potential starting point and then iterates to confirm the match. It returns the index and a boolean indicating if a match was found. ```Go func (seg *segment) lookup(slot []entryPtr, hash16 uint16, key []byte) (idx int, match bool) { idx = entryPtrIdx(slot, hash16) for idx < len(slot) { ``` -------------------------------- ### Del: Delete Cache Entry by Key Source: https://context7.com/coocood/freecache/llms.txt Provides an example of using the Del function to remove a cache entry identified by its key. The function returns true if the key existed and was successfully deleted, and false otherwise. This is essential for cache cleanup and invalidation. ```go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) cache.Set([]byte("to-delete"), []byte("temporary"), 3600) // Delete existing key affected := cache.Del([]byte("to-delete")) fmt.Printf("Deleted existing key: %v\n", affected) // Try to delete non-existent key affected = cache.Del([]byte("nonexistent")) fmt.Printf("Deleted non-existent key: %v\n", affected) // Verify deletion _, err := cache.Get([]byte("to-delete")) fmt.Printf("Key found after deletion: %v\n", err == nil) } ``` -------------------------------- ### GET /cache/get Source: https://github.com/coocood/freecache/wiki/coverage.html Retrieves a value from the cache by key, with optional buffer support and expiration details. ```APIDOC ## GET /cache/get ### Description Retrieves the value associated with a given key. Methods like GetWithExpiration and GetWithBuf allow for memory-efficient access. ### Method GET ### Parameters #### Request Body - **key** ([]byte) - Required - The cache key to look up. - **buf** ([]byte) - Optional - Pre-allocated buffer to copy the value into. ### Response #### Success Response (200) - **value** ([]byte) - The retrieved data. - **expireAt** (uint32) - The expiration timestamp (if using expiration methods). #### Response Example { "value": "data", "expireAt": 1672531200 } ``` -------------------------------- ### TTL: Get Remaining Time-to-Live for a Key Source: https://context7.com/coocood/freecache/llms.txt Demonstrates how to retrieve the remaining time-to-live (TTL) in seconds for a given cache key using the TTL function. It returns 0 for keys without expiration and ErrNotFound for missing or expired keys. This helps in managing cache data lifecycle. ```go package main import ( "fmt" "time" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) cache.Set([]byte("temp-data"), []byte("value"), 120) ttl, err := cache.TTL([]byte("temp-data")) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Time remaining: %d seconds\n", ttl) // Wait a bit and check again time.Sleep(2 * time.Second) ttl, _ = cache.TTL([]byte("temp-data")) fmt.Printf("After 2s: %d seconds remaining\n", ttl) } ``` -------------------------------- ### Get Time-To-Live (TTL) for Cache Entry (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html The 'ttl' function calculates the remaining time-to-live for a cache entry. It retrieves the entry's header, checks if it's expired, and returns the time left. If the entry is not found or has expired, it returns an error. ```Go func (seg *segment) ttl(key []byte, hashVal uint64) (timeLeft uint32, err error) { slotId := uint8(hashVal >> 8) hash16 := uint16(hashVal >> 16) slot := seg.getSlot(slotId) idx, match := seg.lookup(slot, hash16, key) if !match { err = ErrNotFound return } ptr := &slot[idx] var hdrBuf [ENTRY_HDR_SIZE]byte seg.rb.ReadAt(hdrBuf[:], ptr.offset) hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0])) if hdr.expireAt == 0 { return } else { now := seg.timer.Now() if !isExpired(hdr.expireAt, now) { timeLeft = hdr.expireAt - now return } } err = ErrNotFound return } ``` -------------------------------- ### Get Cache Entry Source: https://github.com/coocood/freecache/wiki/coverage.html Retrieves a value from the cache. It locates the entry, checks for expiration, and copies the data into the provided buffer or allocates a new one if necessary. ```go func (seg *segment) get(key, buf []byte, hashVal uint64, peek bool) (value []byte, expireAt uint32, err error) { hdr, ptrOffset, err := seg.locate(key, hashVal, peek) if err != nil { return } expireAt = hdr.expireAt if cap(buf) >= int(hdr.valLen) { value = buf[:hdr.valLen] } else { value = make([]byte, hdr.valLen) } seg.rb.ReadAt(value, ptrOffset+ENTRY_HDR_SIZE+int64(hdr.keyLen)) if !peek { atomic.AddInt64(&seg.hitCount, 1) } return } ``` -------------------------------- ### Get Cache Entry Source: https://github.com/coocood/freecache/wiki/coverage.html Retrieves the value associated with a given key from the cache. Returns ErrNotFound if the key does not exist. ```go func (cache *Cache) Get(key []byte) (value []byte, err error) { hashVal := hashFunc(key) segID := hashVal & segmentAndOpVal cache.locks[segID].Lock() value, _, err = cache.segments[segID].get(key, nil, hashVal, false) cache.locks[segID].Unlock() return } ``` -------------------------------- ### Initialize and Manage Cache in Go Source: https://github.com/coocood/freecache/blob/master/README.md Demonstrates how to instantiate a new cache with a specific memory size, set key-value pairs with expiration, retrieve values, and perform deletion operations. ```go cacheSize := 100 * 1024 * 1024 cache := freecache.NewCache(cacheSize) debug.SetGCPercent(20) key := []byte("abc") val := []byte("def") expire := 60 cache.Set(key, val, expire) got, err := cache.Get(key) if err != nil { fmt.Println(err) } else { fmt.Printf("%s\n", got) } affected := cache.Del(key) fmt.Println("deleted key ", affected) fmt.Println("entry count ", cache.EntryCount()) ``` -------------------------------- ### Create New Cache Instance in Go Source: https://context7.com/coocood/freecache/llms.txt Demonstrates how to create a new FreeCache instance with a specified size in bytes. It also includes a recommendation to adjust the GC percentage for large caches to maintain performance. ```Go package main import ( "fmt" "runtime/debug" "github.com/coocood/freecache" ) func main() { // Create a 100MB cache cacheSize := 100 * 1024 * 1024 cache := freecache.NewCache(cacheSize) // Recommended: Lower GC percentage for large caches debug.SetGCPercent(20) fmt.Printf("Cache created with size: %d bytes\n", cacheSize) // Output: Cache created with size: 104857600 bytes } ``` -------------------------------- ### Freecache Server Initialization (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html Initializes a new Freecache server with a specified cache size. It creates a new cache instance using freecache.NewCache. ```Go //A basic freecache server supports redis protocol package main import ( "bufio" "bytes" "errors" "github.com/coocood/freecache" "io" "log" "net" "net/http" _ "net/http/pprof" "runtime" "runtime/debug" "strconv" "time" ) var ( protocolErr = errors.New("protocol error") CRLF = []byte("\r\n") PING = []byte("ping") DBSIZE = []byte("dbsize") ERROR_UNSUPPORTED = []byte("-ERR unsupported command\r\n") OK = []byte("+OK\r\n") PONG = []byte("+PONG\r\n") GET = []byte("get") SET = []byte("set") SETEX = []byte("setex") DEL = []byte("del") NIL = []byte("$-1\r\n") CZERO = []byte(":0\r\n") CONE = []byte(":1\r\n") BulkSign = []byte("$") ) type Request struct { args [][]byte buf *bytes.Buffer } func (req *Request) Reset() { req.args = req.args[:0] req.buf.Reset() } type operation struct { req Request replyChan chan *bytes.Buffer } type Session struct { server *Server conn net.Conn addr string reader *bufio.Reader replyChan chan *bytes.Buffer } type Server struct { cache *freecache.Cache } func NewServer(cacheSize int) (server *Server) { server = new(Server) server.cache = freecache.NewCache(cacheSize) return } ``` -------------------------------- ### Multi-Get Cache Entries Source: https://github.com/coocood/freecache/wiki/coverage.html Retrieves multiple cache entries efficiently by grouping keys by segment. This reduces lock contention compared to individual Get calls. Note that this operation holds segment locks longer, potentially increasing tail latency for concurrent Get operations. ```go func (cache *Cache) MultiGet(keys [][]byte) (values [][]byte, errs []error) { n := len(keys) if n == 0 { return nil, nil } values = make([][]byte, n) errs = make([]error, n) ``` -------------------------------- ### GET /cache/stats Source: https://github.com/coocood/freecache/wiki/coverage.html Retrieves various performance metrics and statistics for the cache. ```APIDOC ## GET /cache/stats ### Description Retrieves metrics including hit counts, miss counts, hit rates, and entry counts. ### Method GET ### Response #### Success Response (200) - **hitCount** (int64) - Total number of successful lookups. - **missCount** (int64) - Total number of failed lookups. - **hitRate** (float64) - Ratio of hits to total lookups. - **entryCount** (int64) - Total number of items currently in cache. ``` -------------------------------- ### Create Cache with Custom Timer in Go Source: https://context7.com/coocood/freecache/llms.txt Shows how to initialize a FreeCache instance using a custom timer, which is beneficial for testing or precise time control. It highlights the use of `NewCachedTimer` for performance and the importance of stopping the timer. ```Go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cacheSize := 10 * 1024 * 1024 // 10MB // Use cached timer for better performance (updates time every second) timer := freecache.NewCachedTimer() defer timer.Stop() // Important: stop the timer when done cache := freecache.NewCacheCustomTimer(cacheSize, timer) cache.Set([]byte("key"), []byte("value"), 60) value, err := cache.Get([]byte("key")) if err == nil { fmt.Printf("Retrieved: %s\n", value) } // Output: Retrieved: value } ``` -------------------------------- ### Initialize Freecache Instance Source: https://github.com/coocood/freecache/wiki/coverage.html Creates a new freecache instance with a specified size. Ensures a minimum size of 512KB. For large cache sizes, it's recommended to adjust GC settings to manage memory and reduce GC pauses. ```go func NewCache(size int) (cache *Cache) { return NewCacheCustomTimer(size, defaultTimer{}) } ``` -------------------------------- ### Initialize Freecache with Custom Timer Source: https://github.com/coocood/freecache/wiki/coverage.html Initializes a new freecache instance using a custom timer implementation. If no timer is provided, a default timer is used. The cache size is adjusted to a minimum of 512KB if the provided size is smaller. ```go func NewCacheCustomTimer(size int, timer Timer) (cache *Cache) { if size < minBufSize { size = minBufSize } if timer == nil { timer = defaultTimer{} } cache = new(Cache) for i := 0; i < segmentCount; i++ { cache.segments[i] = newSegment(size/segmentCount, i, timer) } return } ``` -------------------------------- ### Monitoring Cache Performance with Statistics in Go Source: https://context7.com/coocood/freecache/llms.txt Provides access to comprehensive cache statistics, including entry count, hit/miss rates, eviction metrics, and more. Statistics can be viewed and reset as needed for performance monitoring. ```go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) // Populate cache and perform operations for i := 0; i < 1000; i++ { key := []byte(fmt.Sprintf("key:%d", i)) cache.Set(key, []byte("value"), 60) } // Simulate some hits and misses for i := 0; i < 100; i++ { cache.Get([]byte(fmt.Sprintf("key:%d", i))) // Hits cache.Get([]byte(fmt.Sprintf("missing:%d", i))) // Misses } // View statistics fmt.Printf("Entry count: %d\n", cache.EntryCount()) fmt.Printf("Hit count: %d\n", cache.HitCount()) fmt.Printf("Miss count: %d\n", cache.MissCount()) fmt.Printf("Lookup count: %d\n", cache.LookupCount()) fmt.Printf("Hit rate: %.2f%%\n", cache.HitRate()*100) fmt.Printf("Evacuate count: %d\n", cache.EvacuateCount()) fmt.Printf("Expired count: %d\n", cache.ExpiredCount()) fmt.Printf("Overwrite count: %d\n", cache.OverwriteCount()) fmt.Printf("Average access time: %d\n", cache.AverageAccessTime()) // Reset statistics cache.ResetStatistics() fmt.Printf("After reset - Hit count: %d\n", cache.HitCount()) // Output: // Entry count: 1000 // Hit count: 100 // Miss count: 100 // Lookup count: 200 // Hit rate: 50.00% // Evacuate count: 0 // Expired count: 0 // Overwrite count: 0 // Average access time: 1705318800 // After reset - Hit count: 0 } ``` -------------------------------- ### Resize Ring Buffer Source: https://github.com/coocood/freecache/wiki/coverage.html Resizes the internal data storage of the ring buffer while maintaining existing data. It adjusts the buffer pointers and re-aligns data to the start of the new slice. ```go func (rb *RingBuf) Resize(newSize int) { if len(rb.data) == newSize { return } newData := make([]byte, newSize) var offset int if rb.end-rb.begin == int64(len(rb.data)) { offset = rb.index } if int(rb.end-rb.begin) > newSize { discard := int(rb.end-rb.begin) - newSize offset = (offset + discard) % len(rb.data) rb.begin = rb.end - int64(newSize) } n := copy(newData, rb.data[offset:]) if n < newSize { copy(newData[n:], rb.data[:offset]) } rb.data = newData rb.index = 0 } ``` -------------------------------- ### Atomic Get or Set Operation Source: https://github.com/coocood/freecache/wiki/coverage.html GetOrSet attempts to retrieve an existing value; if the key is missing, it sets a new key-value pair with an expiration. This ensures atomic behavior within the cache segment. ```go func (cache *Cache) GetOrSet(key, value []byte, expireSeconds int) (retValue []byte, err error) { hashVal := hashFunc(key) segID := hashVal & segmentAndOpVal cache.locks[segID].Lock() defer cache.locks[segID].Unlock() retValue, _, err = cache.segments[segID].get(key, nil, hashVal, false) if err != nil { err = cache.segments[segID].set(key, value, hashVal, expireSeconds) } return } ``` -------------------------------- ### SetAndGet: Atomically Set Value and Retrieve Previous Source: https://context7.com/coocood/freecache/llms.txt Demonstrates how to use SetAndGet to atomically set a new value for a key and retrieve the previous value if it existed. This is useful for implementing operations like atomic swaps. It takes the key, new value, and expiration time as input. ```go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) // Initial set cache.Set([]byte("status"), []byte("pending"), 3600) // Update and get previous value atomically oldValue, found, err := cache.SetAndGet([]byte("status"), []byte("completed"), 3600) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Previous value existed: %v\n", found) fmt.Printf("Previous value: %s\n", oldValue) // Verify new value newValue, _ := cache.Get([]byte("status")) fmt.Printf("New value: %s\n", newValue) } ``` -------------------------------- ### Find Index for Entry Pointer (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html The 'entryPtrIdx' function performs a binary search on a slice of entry pointers to find the correct insertion or lookup index based on a 16-bit hash value. It returns the index where the entry should be or is located. ```Go func entryPtrIdx(slot []entryPtr, hash16 uint16) (idx int) { high := len(slot) for idx < high { mid := (idx + high) >> 1 oldEntry := &slot[mid] if oldEntry.hash16 < hash16 { idx = mid + 1 } else { high = mid } } return } ``` -------------------------------- ### Set Key-Value Pair with Expiration in Go Source: https://context7.com/coocood/freecache/llms.txt Illustrates how to store data in FreeCache using the `Set` method, including specifying an expiration time in seconds. It also covers setting entries without expiration and notes limitations on key and value sizes. ```Go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) key := []byte("user:1001") value := []byte(`{"name":"John","email":"john@example.com"}`) expireSeconds := 300 // Expire in 5 minutes err := cache.Set(key, value, expireSeconds) if err != nil { fmt.Printf("Set error: %v\n", err) return } // Set without expiration (can still be evicted when cache is full) err = cache.Set([]byte("permanent-key"), []byte("permanent-value"), 0) if err != nil { fmt.Printf("Set error: %v\n", err) return } fmt.Println("Values stored successfully") // Output: Values stored successfully } ``` -------------------------------- ### Implement Network Write Loop and Protocol Parsing Source: https://github.com/coocood/freecache/wiki/coverage.html Handles buffered network writes and protocol-specific parsing. Includes methods for reading lines from a buffer and converting byte slices to integers. ```go func (down *Session) writeLoop() { var buffer = bytes.NewBuffer(nil) var replies = make([]*bytes.Buffer, 1) for { buffer.Reset() select { case reply, ok := <-down.replyChan: if !ok { down.conn.Close() return } replies = replies[:1] replies[0] = reply queueLen := len(down.replyChan) for i := 0; i < queueLen; i++ { reply = <-down.replyChan replies = append(replies, reply) } for _, reply := range replies { if reply == nil { buffer.Write(NIL) continue } buffer.Write(reply.Bytes()) } _, err := down.conn.Write(buffer.Bytes()) if err != nil { down.conn.Close() return } } } } func readLine(r *bufio.Reader) ([]byte, error) { p, err := r.ReadSlice('\n') if err != nil { return nil, err } i := len(p) - 2 if i < 0 || p[i] != '\r' { return nil, protocolErr } return p[:i], nil } func btoi(data []byte) (int, error) { if len(data) == 0 { return 0, nil } i := 0 sign := 1 if data[0] == '-' { i++ sign *= -1 } if i >= len(data) { return 0, protocolErr } var l int for ; i < len(data); i++ { c := data[i] if c < '0' || c > '9' { return 0, protocolErr } l = l*10 + int(c-'0') } return sign * l, nil } ``` -------------------------------- ### Atomic Get or Set with GetOrSet in Go Source: https://context7.com/coocood/freecache/llms.txt The GetOrSet function atomically retrieves a value associated with a key. If the key does not exist, it sets the key to a provided value and returns nil for the existing value. If the key exists, it returns the existing value without modification. This ensures thread-safe operations for setting default values. ```Go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) // First call: key doesn't exist, sets the value existing, err := cache.GetOrSet([]byte("counter"), []byte("1"), 3600) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("First call - existing value: %v\n", existing) // nil, value was set // Second call: key exists, returns existing value existing, err = cache.GetOrSet([]byte("counter"), []byte("999"), 3600) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Second call - existing value: %s\n", existing) // Output: // First call - existing value: [] // Second call - existing value: 1 } ``` -------------------------------- ### Go: Initialize New Segment for Cache Source: https://github.com/coocood/freecache/wiki/coverage.html Implements the 'newSegment' function to create and initialize a new segment for the cache. It sets up the ring buffer, segment ID, timer, vacuum length, slot capacity, and the initial slot data array. ```go func newSegment(bufSize int, segId int, timer Timer) (seg segment) { seg.rb = NewRingBuf(bufSize, 0) seg.segId = segId seg.timer = timer seg.vacuumLen = int64(bufSize) seg.slotCap = 1 seg.slotsData = make([]entryPtr, 256*seg.slotCap) return } ``` -------------------------------- ### NewCache Source: https://context7.com/coocood/freecache/llms.txt Initializes a new FreeCache instance with a specific memory size. ```APIDOC ## NewCache ### Description Creates a new FreeCache instance with the specified size in bytes. Minimum size is 512KB. ### Method Constructor ### Parameters #### Request Body - **cacheSize** (int) - Required - Size of the cache in bytes. ### Request Example freecache.NewCache(100 * 1024 * 1024) ``` -------------------------------- ### Integer Key Cache Operations Source: https://github.com/coocood/freecache/wiki/coverage.html Convenience methods for using int64 keys by converting them to byte slices before performing standard cache operations. ```Go func (cache *Cache) SetInt(key int64, value []byte, expireSeconds int) (err error) { var bKey [8]byte binary.LittleEndian.PutUint64(bKey[:], uint64(key)) return cache.Set(bKey[:], value, expireSeconds) } func (cache *Cache) GetInt(key int64) (value []byte, err error) { var bKey [8]byte binary.LittleEndian.PutUint64(bKey[:], uint64(key)) return cache.Get(bKey[:]) } ``` -------------------------------- ### Parse Client Request Protocol (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html This function reads a client request line by line from a buffered reader, parses it according to a specific protocol (likely RESP), and populates a Request object. It validates argument counts and lengths, handling potential errors during parsing and data copying. Dependencies include bufio, bytes, and custom error types like protocolErr. ```Go func (server *Server) ReadClient(r *bufio.Reader, req *Request) (err error) { line, err := readLine(r) if err != nil { return } if len(line) == 0 || line[0] != '*' { err = protocolErr return } argc, err := btoi(line[1:]) if err != nil { return } if argc <= 0 || argc > 4 { err = protocolErr return } var argStarts [4]int var argEnds [4]int req.buf.Write(line) req.buf.Write(CRLF) cursor := len(line) + 2 for i := 0; i < argc; i++ { line, err = readLine(r) if err != nil { return } if len(line) == 0 || line[0] != '$' { err = protocolErr return } var argLen int argLen, err = btoi(line[1:]) if err != nil { return } if argLen < 0 || argLen > 512*1024*1024 { err = protocolErr return } req.buf.Write(line) req.buf.Write(CRLF) cursor += len(line) + 2 err = copyN(req.buf, r, int64(argLen)+2) if err != nil { return } argStarts[i] = cursor argEnds[i] = cursor + argLen cursor += argLen + 2 } data := req.buf.Bytes() for i := 0; i < argc; i++ { req.args = append(req.args, data[argStarts[i]:argEnds[i]]) } lower(req.args[0]) return } ``` -------------------------------- ### Ring Buffer Implementation for Freecache Source: https://github.com/coocood/freecache/wiki/coverage.html Provides a ring buffer implementation used for efficient storage and retrieval of data streams with a fixed size. It supports reading, writing, and slicing data, handling wrap-around scenarios. ```Go package freecache import ( "bytes" "errors" "fmt" "io" ) var ErrOutOfRange = errors.New("out of range") // Ring buffer has a fixed size, when data exceeds the // size, old data will be overwritten by new data. // It only contains the data in the stream from begin to end type RingBuf struct { begin int64 // beginning offset of the data stream. end int64 // ending offset of the data stream. data []byte index int //range from '0' to 'len(rb.data)-1' } func NewRingBuf(size int, begin int64) (rb RingBuf) { rb.data = make([]byte, size) rb.Reset(begin) return } // Reset the ring buffer // // Parameters: // begin: beginning offset of the data stream func (rb *RingBuf) Reset(begin int64) { rb.begin = begin rb.end = begin rb.index = 0 } // Create a copy of the buffer. func (rb *RingBuf) Dump() []byte { dump := make([]byte, len(rb.data)) copy(dump, rb.data) return dump } func (rb *RingBuf) String() string { return fmt.Sprintf("[size:%v, start:%v, end:%v, index:%v]", len(rb.data), rb.begin, rb.end, rb.index) } func (rb *RingBuf) Size() int64 { return int64(len(rb.data)) } func (rb *RingBuf) Begin() int64 { return rb.begin } func (rb *RingBuf) End() int64 { return rb.end } // read up to len(p), at off of the data stream. func (rb *RingBuf) ReadAt(p []byte, off int64) (n int, err error) { if off > rb.end || off < rb.begin { err = ErrOutOfRange return } readOff := rb.getDataOff(off) readEnd := readOff + int(rb.end-off) if readEnd <= len(rb.data) { n = copy(p, rb.data[readOff:readEnd]) } else { n = copy(p, rb.data[readOff:]) if n < len(p) { n += copy(p[n:], rb.data[:readEnd-len(rb.data)]) } } if n < len(p) { err = io.EOF } return } func (rb *RingBuf) getDataOff(off int64) int { var dataOff int if rb.end-rb.begin < int64(len(rb.data)) { dataOff = int(off - rb.begin) } else { dataOff = rb.index + int(off-rb.begin) } if dataOff >= len(rb.data) { dataOff -= len(rb.data) } return dataOff } // Slice returns a slice of the supplied range of the ring buffer. It will // not alloc unless the requested range wraps the ring buffer. func (rb *RingBuf) Slice(off, length int64) ([]byte, error) { if off > rb.end || off < rb.begin { return nil, ErrOutOfRange } readOff := rb.getDataOff(off) readEnd := readOff + int(length) if readEnd <= len(rb.data) { return rb.data[readOff:readEnd:readEnd], nil } buf := make([]byte, length) n := copy(buf, rb.data[readOff:]) if n < int(length) { n += copy(buf[n:], rb.data[:readEnd-len(rb.data)]) } if n < int(length) { return nil, io.EOF } return buf, nil } func (rb *RingBuf) Write(p []byte) (n int, err error) { if len(p) > len(rb.data) { err = ErrOutOfRange return } for n < len(p) { written := copy(rb.data[rb.index:], p[n:]) rb.end += int64(written) n += written ``` -------------------------------- ### Integer Key Operations (SetInt, GetInt, DelInt) in Go Source: https://context7.com/coocood/freecache/llms.txt Provides convenience methods for using int64 keys in the cache. These methods internally convert integer keys to byte slices. Includes setting, retrieving, and deleting entries using integer keys. ```go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) // Use integer key for user ID userID := int64(12345) userData := []byte(`{"name":"Bob","role":"admin"}`) err := cache.SetInt(userID, userData, 3600) if err != nil { fmt.Printf("SetInt error: %v\n", err) return } // Retrieve by integer key value, err := cache.GetInt(userID) if err != nil { fmt.Printf("GetInt error: %v\n", err) return } fmt.Printf("User %d: %s\n", userID, value) // Get with expiration value, expireAt, _ := cache.GetIntWithExpiration(userID) fmt.Printf("Expires at: %d\n", expireAt) // Delete by integer key deleted := cache.DelInt(userID) fmt.Printf("Deleted: %v\n", deleted) // Output: // User 12345: {"name":"Bob","role":"admin"} // Expires at: 1705322400 // Deleted: true } ``` -------------------------------- ### Touch: Update Expiration Time of Existing Key Source: https://context7.com/coocood/freecache/llms.txt Shows how to use the Touch function to update the expiration time of an existing cache key without altering its value. If the key does not exist, it returns an ErrNotFound error. This is useful for extending the lifetime of cached data. ```go package main import ( "fmt" "github.com/coocood/freecache" ) func main() { cache := freecache.NewCache(10 * 1024 * 1024) // Set with short expiration cache.Set([]byte("session"), []byte("user_data"), 60) ttl, _ := cache.TTL([]byte("session")) fmt.Printf("Initial TTL: %d seconds\n", ttl) // Extend expiration to 1 hour err := cache.Touch([]byte("session"), 3600) if err != nil { fmt.Printf("Touch error: %v\n", err) return } ttl, _ = cache.TTL([]byte("session")) fmt.Printf("Extended TTL: %d seconds\n", ttl) } ``` -------------------------------- ### Read Data from Network Buffer (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html This function reads data from a network reader into a buffer. It handles cases where the data size is less than or equal to 512 bytes by using a fixed-size buffer, or uses io.CopyN for larger data sizes. It ensures all data is written to the provided buffer. ```Go func readFromBuffer(r io.Reader, buffer *bytes.Buffer, n int64) (err error) { if n <= 512 { var buf [512]byte _, err = r.Read(buf[:n]) if err != nil { return } buffer.Write(buf[:n]) } else { _, err = io.CopyN(buffer, r, n) } return } ``` -------------------------------- ### View Cache Entry (Zero-Copy) Source: https://github.com/coocood/freecache/wiki/coverage.html Provides zero-copy access to a cache entry's value by invoking a callback function with the slice directly from the ring buffer, avoiding intermediate allocations. ```go func (seg *segment) view(key []byte, fn func([]byte) error, hashVal uint64, peek bool) (err error) { hdr, ptrOffset, err := seg.locate(key, hashVal, peek) if err != nil { return } start := ptrOffset + ENTRY_HDR_SIZE + int64(hdr.keyLen) val, err := seg.rb.Slice(start, int64(hdr.valLen)) if err != nil { return err } return fn(val) } ``` -------------------------------- ### Freecache Segment Statistics and Reset (Go) Source: https://github.com/coocood/freecache/wiki/coverage.html Provides functions to reset cache segment statistics and clear the cache. These are essential for monitoring cache performance and managing its state. ```Go func (seg *segment) resetStatistics() { atomic.StoreInt64(&seg.totalEvacuate, 0) atomic.StoreInt64(&seg.totalExpired, 0) atomic.StoreInt64(&seg.overwrites, 0) atomic.StoreInt64(&seg.hitCount, 0) atomic.StoreInt64(&seg.missCount, 0) } func (seg *segment) clear() { bufSize := len(seg.rb.data) seg.rb.Reset(0) seg.vacuumLen = int64(bufSize) seg.slotCap = 1 seg.slotsData = make([]entryPtr, 256*seg.slotCap) for i := 0; i < len(seg.slotLens); i++ { seg.slotLens[i] = 0 } atomic.StoreInt64(&seg.hitCount, 0) atomic.StoreInt64(&seg.missCount, 0) atomic.StoreInt64(&seg.entryCount, 0) atomic.StoreInt64(&seg.totalCount, 0) atomic.StoreInt64(&seg.totalTime, 0) atomic.StoreInt64(&seg.totalEvacuate, 0) atomic.StoreInt64(&seg.totalExpired, 0) atomic.StoreInt64(&seg.overwrites, 0) } ```