package ledis import ( "encoding/binary" "errors" "github.com/siddontang/go-leveldb/leveldb" "time" ) var mapExpMetaType = map[byte]byte{ kvExpType: kvExpMetaType, lExpType: lExpMetaType, hExpType: hExpMetaType, zExpType: zExpMetaType} type retireCallback func(*tx, []byte) int64 type Elimination struct { db *DB exp2Tx map[byte]*tx exp2Retire map[byte]retireCallback } var errExpType = errors.New("invalid expire type") func (db *DB) expEncodeTimeKey(expType byte, key []byte, when int64) []byte { // format : db[8] / expType[8] / when[64] / key[...] buf := make([]byte, len(key)+10) buf[0] = db.index buf[1] = expType pos := 2 binary.BigEndian.PutUint64(buf[pos:], uint64(when)) pos += 8 copy(buf[pos:], key) return buf } func (db *DB) expEncodeMetaKey(expType byte, key []byte) []byte { // format : db[8] / expType[8] / key[...] buf := make([]byte, len(key)+2) buf[0] = db.index buf[1] = expType pos := 2 copy(buf[pos:], key) return buf } // usage : separate out the original key func (db *DB) expDecodeMetaKey(mk []byte) []byte { if len(mk) <= 2 { // check db ? check type ? return nil } return mk[2:] } func (db *DB) expire(t *tx, expType byte, key []byte, duration int64) { db.expireAt(t, expType, key, time.Now().Unix()+duration) } func (db *DB) expireAt(t *tx, expType byte, key []byte, when int64) { mk := db.expEncodeMetaKey(expType+1, key) tk := db.expEncodeTimeKey(expType, key, when) t.Put(tk, mk) t.Put(mk, PutInt64(when)) } func (db *DB) ttl(expType byte, key []byte) (t int64, err error) { mk := db.expEncodeMetaKey(expType+1, key) if t, err = Int64(db.db.Get(mk)); err != nil || t == 0 { t = -1 } else { t -= time.Now().Unix() if t <= 0 { t = -1 } // if t == -1 : to remove ???? } return t, err } func (db *DB) rmExpire(t *tx, expType byte, key []byte) { mk := db.expEncodeMetaKey(expType+1, key) when, err := Int64(db.db.Get(mk)) if err == nil && when > 0 { tk := db.expEncodeTimeKey(expType, key, when) t.Delete(mk) t.Delete(tk) } } func (db *DB) expFlush(t *tx, expType byte) (err error) { expMetaType, ok := mapExpMetaType[expType] if !ok { return errExpType } drop := 0 minKey := make([]byte, 2) minKey[0] = db.index minKey[1] = expType maxKey := make([]byte, 2) maxKey[0] = db.index maxKey[1] = expMetaType + 1 it := db.db.Iterator(minKey, maxKey, leveldb.RangeROpen, 0, -1) for ; it.Valid(); it.Next() { t.Delete(it.Key()) drop++ if drop&1023 == 0 { if err = t.Commit(); err != nil { return } } } err = t.Commit() return } ////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////// func newEliminator(db *DB) *Elimination { eli := new(Elimination) eli.db = db eli.exp2Tx = make(map[byte]*tx) eli.exp2Retire = make(map[byte]retireCallback) return eli } func (eli *Elimination) regRetireContext(expType byte, t *tx, onRetire retireCallback) { eli.exp2Tx[expType] = t eli.exp2Retire[expType] = onRetire } // call by outside ... (from *db to another *db) func (eli *Elimination) active() { now := time.Now().Unix() db := eli.db dbGet := db.db.Get expKeys := make([][]byte, 0, 1024) expTypes := [...]byte{kvExpType, lExpType, hExpType, zExpType} for _, et := range expTypes { // search those keys' which expire till the moment minKey := db.expEncodeTimeKey(et, nil, 0) maxKey := db.expEncodeTimeKey(et, nil, now+1) expKeys = expKeys[0:0] t, _ := eli.exp2Tx[et] onRetire, _ := eli.exp2Retire[et] if t == nil || onRetire == nil { // todo : log error continue } it := db.db.Iterator(minKey, maxKey, leveldb.RangeROpen, 0, -1) for it.Valid() { for i := 1; i < 512 && it.Valid(); i++ { expKeys = append(expKeys, it.Key(), it.Value()) it.Next() } var cnt int = len(expKeys) if cnt == 0 { continue } t.Lock() var mk, ek, k []byte for i := 0; i < cnt; i += 2 { ek, mk = expKeys[i], expKeys[i+1] if exp, err := Int64(dbGet(mk)); err == nil { // check expire again if exp > now { continue } // delete keys k = db.expDecodeMetaKey(mk) onRetire(t, k) t.Delete(ek) t.Delete(mk) } } t.Commit() t.Unlock() } // end : it } // end : expType return }