(done) replace redigo with go-redis

* replace redis keys with RawString encoded values (delete SerializedPeer)
* merge peers got from pre-hools with store data
This commit is contained in:
Lawrence, Rendall
2022-04-14 00:55:58 +03:00
parent 1fcddf5102
commit 781fa9440f
20 changed files with 490 additions and 455 deletions
+33 -30
View File
@@ -186,8 +186,8 @@ type peerShard struct {
type swarm struct {
// map serialized peer to mtime
seeders map[storage.SerializedPeer]int64
leechers map[storage.SerializedPeer]int64
seeders map[string]int64
leechers map[string]int64
}
type store struct {
@@ -214,7 +214,7 @@ func (ps *store) populateProm() {
s.RUnlock()
}
storage.PromInfohashesCount.Set(float64(numInfohashes))
storage.PromInfoHashesCount.Set(float64(numInfohashes))
storage.PromSeedersCount.Set(float64(numSeeders))
storage.PromLeechersCount.Set(float64(numLeechers))
}
@@ -246,15 +246,15 @@ func (ps *store) PutSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
default:
}
pk := storage.NewSerializedPeer(p)
pk := p.RawString()
shard := ps.shards[ps.shardIndex(ih, p.IP.AddressFamily)]
shard.Lock()
if _, ok := shard.swarms[ih]; !ok {
shard.swarms[ih] = swarm{
seeders: make(map[storage.SerializedPeer]int64),
leechers: make(map[storage.SerializedPeer]int64),
seeders: make(map[string]int64),
leechers: make(map[string]int64),
}
}
@@ -277,7 +277,7 @@ func (ps *store) DeleteSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
default:
}
pk := storage.NewSerializedPeer(p)
pk := p.RawString()
shard := ps.shards[ps.shardIndex(ih, p.IP.AddressFamily)]
shard.Lock()
@@ -310,15 +310,15 @@ func (ps *store) PutLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error {
default:
}
pk := storage.NewSerializedPeer(p)
pk := p.RawString()
shard := ps.shards[ps.shardIndex(ih, p.IP.AddressFamily)]
shard.Lock()
if _, ok := shard.swarms[ih]; !ok {
shard.swarms[ih] = swarm{
seeders: make(map[storage.SerializedPeer]int64),
leechers: make(map[storage.SerializedPeer]int64),
seeders: make(map[string]int64),
leechers: make(map[string]int64),
}
}
@@ -341,7 +341,7 @@ func (ps *store) DeleteLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error
default:
}
pk := storage.NewSerializedPeer(p)
pk := p.RawString()
shard := ps.shards[ps.shardIndex(ih, p.IP.AddressFamily)]
shard.Lock()
@@ -374,15 +374,15 @@ func (ps *store) GraduateLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) erro
default:
}
pk := storage.NewSerializedPeer(p)
pk := p.RawString()
shard := ps.shards[ps.shardIndex(ih, p.IP.AddressFamily)]
shard.Lock()
if _, ok := shard.swarms[ih]; !ok {
shard.swarms[ih] = swarm{
seeders: make(map[storage.SerializedPeer]int64),
leechers: make(map[storage.SerializedPeer]int64),
seeders: make(map[string]int64),
leechers: make(map[string]int64),
}
}
@@ -426,8 +426,8 @@ func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int,
if numWant == 0 {
break
}
peers = append(peers, pk.ToPeer())
p, _ := bittorrent.NewPeer(pk)
peers = append(peers, p)
numWant--
}
} else {
@@ -437,15 +437,15 @@ func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int,
if numWant == 0 {
break
}
peers = append(peers, pk.ToPeer())
p, _ := bittorrent.NewPeer(pk)
peers = append(peers, p)
numWant--
}
// Append leechers until we reach numWant.
if numWant > 0 {
leechers := shard.swarms[ih].leechers
announcerPK := storage.NewSerializedPeer(announcer)
announcerPK := announcer.RawString()
for pk := range leechers {
if pk == announcerPK {
continue
@@ -454,8 +454,8 @@ func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int,
if numWant == 0 {
break
}
peers = append(peers, pk.ToPeer())
p, _ := bittorrent.NewPeer(pk)
peers = append(peers, p)
numWant--
}
}
@@ -500,38 +500,40 @@ func asKey(in any) any {
return fmt.Sprint(in)
}
func (ps *store) Put(ctx string, key, value any) {
func (ps *store) Put(ctx string, value storage.Entry) error {
m, _ := ps.contexts.LoadOrStore(ctx, new(sync.Map))
m.(*sync.Map).Store(asKey(key), value)
m.(*sync.Map).Store(value.Key, value.Value)
return nil
}
func (ps *store) Contains(ctx string, key any) bool {
func (ps *store) Contains(ctx string, key string) (bool, error) {
var exist bool
if m, found := ps.contexts.Load(ctx); found {
_, exist = m.(*sync.Map).Load(asKey(key))
}
return exist
return exist, nil
}
func (ps *store) BulkPut(ctx string, pairs ...storage.Pair) {
func (ps *store) BulkPut(ctx string, pairs ...storage.Entry) error {
if len(pairs) > 0 {
c, _ := ps.contexts.LoadOrStore(ctx, new(sync.Map))
m := c.(*sync.Map)
for _, p := range pairs {
m.Store(asKey(p.Left), p.Right)
m.Store(asKey(p.Key), p.Value)
}
}
return nil
}
func (ps *store) Load(ctx string, key any) any {
func (ps *store) Load(ctx string, key string) (any, error) {
var v any
if m, found := ps.contexts.Load(ctx); found {
v, _ = m.(*sync.Map).Load(asKey(key))
}
return v
return v, nil
}
func (ps *store) Delete(ctx string, keys ...any) {
func (ps *store) Delete(ctx string, keys ...string) error {
if len(keys) > 0 {
if m, found := ps.contexts.Load(ctx); found {
m := m.(*sync.Map)
@@ -540,6 +542,7 @@ func (ps *store) Delete(ctx string, keys ...any) {
}
}
}
return nil
}
// collectGarbage deletes all Peers from the Storage which are older than the
-54
View File
@@ -1,54 +0,0 @@
package storage
import (
"encoding/binary"
"net"
"github.com/sot-tech/mochi/bittorrent"
)
// Pair - some key-value pair, used for BulkPut
type Pair struct {
Left, Right any
}
// SerializedPeer concatenation of PeerID, net port and IP-address
type SerializedPeer string
// NewSerializedPeer builds SerializedPeer from bittorrent.Peer
func NewSerializedPeer(p bittorrent.Peer) SerializedPeer {
b := make([]byte, bittorrent.PeerIDLen+2+len(p.IP.IP))
copy(b[:bittorrent.PeerIDLen], p.ID[:])
binary.BigEndian.PutUint16(b[bittorrent.PeerIDLen:bittorrent.PeerIDLen+2], p.Port)
copy(b[bittorrent.PeerIDLen+2:], p.IP.IP)
return SerializedPeer(b)
}
// ToPeer parses SerializedPeer to bittorrent.Peer
func (pk SerializedPeer) ToPeer() bittorrent.Peer {
peerID, err := bittorrent.NewPeerID([]byte(pk[:bittorrent.PeerIDLen]))
if err != nil {
panic(err)
}
peer := bittorrent.Peer{
ID: peerID,
Port: binary.BigEndian.Uint16([]byte(pk[bittorrent.PeerIDLen : bittorrent.PeerIDLen+2])),
IP: bittorrent.IP{IP: net.IP(pk[bittorrent.PeerIDLen+2:])},
}
if ip := peer.IP.To4(); ip != nil {
peer.IP.IP = ip
peer.IP.AddressFamily = bittorrent.IPv4
} else if len(peer.IP.IP) == net.IPv6len { // implies toReturn.IP.To4() == nil
peer.IP.AddressFamily = bittorrent.IPv6
} else {
panic("IP is neither v4 nor v6")
}
return peer
}
func (pk SerializedPeer) String() string {
return string(pk)
}
+7 -7
View File
@@ -6,7 +6,7 @@ func init() {
// Register the metrics.
prometheus.MustRegister(
PromGCDurationMilliseconds,
PromInfohashesCount,
PromInfoHashesCount,
PromSeedersCount,
PromLeechersCount,
)
@@ -16,29 +16,29 @@ var (
// PromGCDurationMilliseconds is a histogram used by storage to record the
// durations of execution time required for removing expired peers.
PromGCDurationMilliseconds = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "chihaya_storage_gc_duration_milliseconds",
Name: "mochi_storage_gc_duration_milliseconds",
Help: "The time it takes to perform storage garbage collection",
Buckets: prometheus.ExponentialBuckets(9.375, 2, 10),
})
// PromInfohashesCount is a gauge used to hold the current total amount of
// PromInfoHashesCount is a gauge used to hold the current total amount of
// unique swarms being tracked by a storage.
PromInfohashesCount = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "chihaya_storage_infohashes_count",
PromInfoHashesCount = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "mochi_storage_infohashes_count",
Help: "The number of Infohashes tracked",
})
// PromSeedersCount is a gauge used to hold the current total amount of
// unique seeders per swarm.
PromSeedersCount = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "chihaya_storage_seeders_count",
Name: "mochi_storage_seeders_count",
Help: "The number of seeders tracked",
})
// PromLeechersCount is a gauge used to hold the current total amount of
// unique leechers per swarm.
PromLeechersCount = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "chihaya_storage_leechers_count",
Name: "mochi_storage_leechers_count",
Help: "The number of leechers tracked",
})
)
+188 -172
View File
@@ -55,6 +55,7 @@ const (
defaultConnectTimeout = time.Second * 15
)
// ErrSentinelAndClusterChecked returned from initializer if both Config.Sentinel and Config.Cluster provided
var ErrSentinelAndClusterChecked = errors.New("unable to use both cluster and sentinel mode")
func init() {
@@ -118,7 +119,6 @@ func (cfg Config) LogFields() log.Fields {
//
// This function warns to the logger when a value is changed.
func (cfg Config) Validate() (Config, error) {
if cfg.Sentinel && cfg.Cluster {
return cfg, ErrSentinelAndClusterChecked
}
@@ -203,7 +203,7 @@ func (cfg Config) Validate() (Config, error) {
func connect(cfg Config) (*store, error) {
var err error
db := &store{
// FIXME: get context from parent
// FIXME: get context from parent and put into GC, middleware functions should use own ctx
ctx: context.TODO(),
}
switch {
@@ -243,8 +243,8 @@ func connect(cfg Config) (*store, error) {
if err = db.con.Ping(db.ctx).Err(); err == nil && !errors.Is(err, redis.Nil) {
err = nil
} else {
db = nil
_ = db.con.Close()
db = nil
}
return db, err
}
@@ -287,9 +287,7 @@ func (ps *store) runGC(gcInterval, peerLifeTime time.Duration) {
case <-time.After(gcInterval):
before := time.Now().Add(-peerLifeTime)
log.Debug("storage: purging peers with no announces since", log.Fields{"before": before})
if err := ps.collectGarbage(before); err != nil {
log.Error("storage: collectGarbage error", log.Fields{"before": before, "error": err})
}
ps.collectGarbage(before)
}
}
}
@@ -321,39 +319,44 @@ type store struct {
var groups = []string{bittorrent.IPv4.String(), bittorrent.IPv6.String()}
func leecherInfohashKey(af, ih string) string {
return af + "_L_" + ih
// leecherInfoHashKey generates string IPvN_L_hash
func leecherInfoHashKey(addressFamily, infoHash string) string {
return addressFamily + "_L_" + infoHash
}
func seederInfohashKey(af, ih string) string {
return af + "_S_" + ih
// seederInfoHashKey generates string IPvN_S_hash
func seederInfoHashKey(addressFamily, infoHash string) string {
return addressFamily + "_S_" + infoHash
}
func infohashCountKey(af string) string {
return af + "_infohash_count"
// seederInfoHashKey generates string IPvN_infohash_count
func infoHashCountKey(addressFamily string) string {
return addressFamily + "_infohash_count"
}
func seederCountKey(af string) string {
return af + "_S_count"
// seederInfoHashKey generates string IPvN_L_count
func leecherCountKey(addressFamily string) string {
return addressFamily + "_L_count"
}
func leecherCountKey(af string) string {
return af + "_L_count"
// seederInfoHashKey generates string IPvN_S_count
func seederCountKey(addressFamily string) string {
return addressFamily + "_S_count"
}
// populateProm aggregates metrics over all groups and then posts them to
// prometheus.
func (ps *store) populateProm() {
var numInfohashes, numSeeders, numLeechers int64
var numInfoHashes, numSeeders, numLeechers int64
for _, group := range groups {
if n, err := ps.con.Get(ps.ctx, infohashCountKey(group)).Int64(); err != nil && !errors.Is(err, redis.Nil) {
if n, err := ps.con.Get(ps.ctx, infoHashCountKey(group)).Int64(); err != nil && !errors.Is(err, redis.Nil) {
log.Error("storage: GET counter failure", log.Fields{
"key": infohashCountKey(group),
"key": infoHashCountKey(group),
"error": err,
})
} else {
numInfohashes += n
numInfoHashes += n
}
if n, err := ps.con.Get(ps.ctx, seederCountKey(group)).Int64(); err != nil && !errors.Is(err, redis.Nil) {
log.Error("storage: GET counter failure", log.Fields{
@@ -373,7 +376,7 @@ func (ps *store) populateProm() {
}
}
storage.PromInfohashesCount.Set(float64(numInfohashes))
storage.PromInfoHashesCount.Set(float64(numInfoHashes))
storage.PromSeedersCount.Set(float64(numSeeders))
storage.PromLeechersCount.Set(float64(numLeechers))
}
@@ -386,7 +389,7 @@ func (ps *store) tx(txf func(tx redis.Pipeliner) error) (err error) {
if pipe, txErr := ps.con.TxPipelined(ps.ctx, txf); txErr == nil {
errs := make([]string, 0)
for _, c := range pipe {
if err := c.Err(); err == nil {
if err := c.Err(); err != nil {
errs = append(errs, err.Error())
}
}
@@ -409,16 +412,15 @@ func asNil(err error) error {
func (ps *store) PutSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
addressFamily := p.IP.AddressFamily.String()
log.Debug("storage: PutSeeder", log.Fields{
"InfoHash": ih.String(),
"InfoHash": ih,
"Peer": p,
})
peerKey := storage.NewSerializedPeer(p).String()
encodedSeederInfoHash := seederInfohashKey(addressFamily, ih.String())
encodedSeederInfoHash := seederInfoHashKey(addressFamily, ih.RawString())
now := ps.getClock()
return ps.tx(func(tx redis.Pipeliner) (err error) {
if err = tx.HSet(ps.ctx, encodedSeederInfoHash, peerKey, now).Err(); err != nil {
if err = tx.HSet(ps.ctx, encodedSeederInfoHash, p.RawString(), now).Err(); err != nil {
return
}
if err = ps.con.Incr(ps.ctx, seederCountKey(addressFamily)).Err(); err != nil {
@@ -427,7 +429,7 @@ func (ps *store) PutSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
if err = ps.con.HSet(ps.ctx, addressFamily, encodedSeederInfoHash, now).Err(); err != nil {
return
}
err = ps.con.Incr(ps.ctx, infohashCountKey(addressFamily)).Err()
err = ps.con.Incr(ps.ctx, infoHashCountKey(addressFamily)).Err()
return
})
}
@@ -435,13 +437,12 @@ func (ps *store) PutSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
func (ps *store) DeleteSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
addressFamily := p.IP.AddressFamily.String()
log.Debug("storage: DeleteSeeder", log.Fields{
"InfoHash": ih.String(),
"InfoHash": ih,
"Peer": p,
})
peerKey := storage.NewSerializedPeer(p).String()
encodedSeederInfoHash := seederInfohashKey(addressFamily, ih.String())
deleted, err := ps.con.HDel(ps.ctx, encodedSeederInfoHash, peerKey).Uint64()
encodedSeederInfoHash := seederInfoHashKey(addressFamily, ih.RawString())
deleted, err := ps.con.HDel(ps.ctx, encodedSeederInfoHash, p.RawString()).Uint64()
err = asNil(err)
if err == nil {
if deleted == 0 {
@@ -457,17 +458,16 @@ func (ps *store) DeleteSeeder(ih bittorrent.InfoHash, p bittorrent.Peer) error {
func (ps *store) PutLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error {
addressFamily := p.IP.AddressFamily.String()
log.Debug("storage: PutLeecher", log.Fields{
"InfoHash": ih.String(),
"InfoHash": ih,
"Peer": p,
})
// Update the peer in the swarm.
encodedLeecherInfoHash := leecherInfohashKey(addressFamily, ih.String())
peerKey := storage.NewSerializedPeer(p).String()
encodedLeecherInfoHash := leecherInfoHashKey(addressFamily, ih.RawString())
now := ps.getClock()
return ps.tx(func(tx redis.Pipeliner) (err error) {
if err = tx.HSet(ps.ctx, encodedLeecherInfoHash, peerKey, now).Err(); err != nil {
if err = tx.HSet(ps.ctx, encodedLeecherInfoHash, p.RawString(), now).Err(); err != nil {
return
}
if err = tx.HSet(ps.ctx, addressFamily, encodedLeecherInfoHash, now).Err(); err != nil {
@@ -481,14 +481,13 @@ func (ps *store) PutLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error {
func (ps *store) DeleteLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error {
addressFamily := p.IP.AddressFamily.String()
log.Debug("storage: DeleteLeecher", log.Fields{
"InfoHash": ih.String(),
"InfoHash": ih,
"Peer": p,
})
peerKey := storage.NewSerializedPeer(p).String()
encodedLeecherInfoHash := leecherInfohashKey(addressFamily, ih.String())
encodedLeecherInfoHash := leecherInfoHashKey(addressFamily, ih.RawString())
deleted, err := ps.con.HDel(ps.ctx, encodedLeecherInfoHash, peerKey).Uint64()
deleted, err := ps.con.HDel(ps.ctx, encodedLeecherInfoHash, p.RawString()).Uint64()
err = asNil(err)
if err == nil {
if deleted == 0 {
@@ -504,14 +503,14 @@ func (ps *store) DeleteLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error
func (ps *store) GraduateLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) error {
addressFamily := p.IP.AddressFamily.String()
log.Debug("storage: GraduateLeecher", log.Fields{
"InfoHash": ih.String(),
"InfoHash": ih,
"Peer": p,
})
encodedInfoHash := ih.String()
encodedLeecherInfoHash := leecherInfohashKey(addressFamily, encodedInfoHash)
encodedSeederInfoHash := seederInfohashKey(addressFamily, encodedInfoHash)
peerKey := storage.NewSerializedPeer(p).String()
encodedInfoHash := ih.RawString()
encodedLeecherInfoHash := leecherInfoHashKey(addressFamily, encodedInfoHash)
encodedSeederInfoHash := seederInfoHashKey(addressFamily, encodedInfoHash)
peerKey := p.RawString()
now := ps.getClock()
return ps.tx(func(tx redis.Pipeliner) error {
@@ -532,7 +531,7 @@ func (ps *store) GraduateLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) erro
err = tx.HSet(ps.ctx, addressFamily, encodedSeederInfoHash, now).Err()
}
if err == nil {
err = tx.Incr(ps.ctx, infohashCountKey(addressFamily)).Err()
err = tx.Incr(ps.ctx, infoHashCountKey(addressFamily)).Err()
}
return err
})
@@ -541,15 +540,15 @@ func (ps *store) GraduateLeecher(ih bittorrent.InfoHash, p bittorrent.Peer) erro
func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int, announcer bittorrent.Peer) (peers []bittorrent.Peer, err error) {
addressFamily := announcer.IP.AddressFamily.String()
log.Debug("storage: AnnouncePeers", log.Fields{
"InfoHash": ih.String(),
"InfoHash": ih,
"seeder": seeder,
"numWant": numWant,
"Peer": announcer,
})
encodedInfoHash := ih.String()
encodedLeecherInfoHash := leecherInfohashKey(addressFamily, encodedInfoHash)
encodedSeederInfoHash := seederInfohashKey(addressFamily, encodedInfoHash)
encodedInfoHash := ih.RawString()
encodedLeecherInfoHash := leecherInfoHashKey(addressFamily, encodedInfoHash)
encodedSeederInfoHash := seederInfoHashKey(addressFamily, encodedInfoHash)
leechers, err := ps.con.HKeys(ps.ctx, encodedLeecherInfoHash).Result()
err = asNil(err)
@@ -573,9 +572,12 @@ func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int,
if numWant == 0 {
break
}
peers = append(peers, storage.SerializedPeer(peerKey).ToPeer())
numWant--
if p, err := bittorrent.NewPeer(peerKey); err == nil {
peers = append(peers, p)
numWant--
} else {
log.Error("storage: unable to decode leecher", log.Fields{"peer": peerKey})
}
}
} else {
// Append as many seeders as possible.
@@ -583,22 +585,28 @@ func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int,
if numWant == 0 {
break
}
peers = append(peers, storage.SerializedPeer(peerKey).ToPeer())
numWant--
if p, err := bittorrent.NewPeer(peerKey); err == nil {
peers = append(peers, p)
numWant--
} else {
log.Error("storage: unable to decode seeder", log.Fields{"peer": peerKey})
}
}
// Append leechers until we reach numWant.
if numWant > 0 {
announcerPK := storage.NewSerializedPeer(announcer).String()
announcerPK := announcer.RawString()
for _, peerKey := range leechers {
if peerKey != announcerPK {
if numWant == 0 {
break
}
peers = append(peers, storage.SerializedPeer(peerKey).ToPeer())
numWant--
if p, err := bittorrent.NewPeer(peerKey); err == nil {
peers = append(peers, p)
numWant--
} else {
log.Error("storage: unable to decode leecher", log.Fields{"peer": peerKey})
}
}
}
}
@@ -610,9 +618,9 @@ func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int,
func (ps *store) ScrapeSwarm(ih bittorrent.InfoHash, af bittorrent.AddressFamily) (resp bittorrent.Scrape) {
resp.InfoHash = ih
addressFamily := af.String()
encodedInfoHash := ih.String()
encodedLeecherInfoHash := leecherInfohashKey(addressFamily, encodedInfoHash)
encodedSeederInfoHash := seederInfohashKey(addressFamily, encodedInfoHash)
encodedInfoHash := ih.RawString()
encodedLeecherInfoHash := leecherInfoHashKey(addressFamily, encodedInfoHash)
encodedSeederInfoHash := seederInfoHashKey(addressFamily, encodedInfoHash)
leechersLen, err := ps.con.HLen(ps.ctx, encodedLeecherInfoHash).Result()
err = asNil(err)
@@ -640,70 +648,61 @@ func (ps *store) ScrapeSwarm(ih bittorrent.InfoHash, af bittorrent.AddressFamily
return
}
func (ps *store) Put(ctx string, key, value any) {
err := ps.con.HSet(ps.ctx, ctx, key, value).Err()
if err != nil {
panic(err)
}
func (ps *store) Put(ctx string, value storage.Entry) error {
return ps.con.HSet(ps.ctx, ctx, value.Key, value.Value).Err()
}
func (ps *store) Contains(ctx string, key any) bool {
func (ps *store) Contains(ctx string, key string) (bool, error) {
exist, err := ps.con.HExists(ps.ctx, ctx, key).Result()
if err != nil {
panic(err)
}
return exist
return exist, asNil(err)
}
const argNumErrorMsg = "ERR wrong number of arguments"
func (ps *store) BulkPut(ctx string, pairs ...storage.Pair) {
func (ps *store) BulkPut(ctx string, pairs ...storage.Entry) (err error) {
if l := len(pairs); l > 0 {
args := make([]any, 0, l*2)
for _, p := range pairs {
args = append(args, p.Left, p.Right)
args = append(args, p.Key, p.Value)
}
err := ps.con.HSet(ps.ctx, ctx, args...).Err()
err = ps.con.HSet(ps.ctx, ctx, args...).Err()
if err != nil {
if strings.Contains(err.Error(), argNumErrorMsg) {
log.Warn("This REDIS version/implementation does not support variadic arguments for HSET")
for _, p := range pairs {
if err := ps.con.HSet(ps.ctx, ctx, p.Left, p.Right).Err(); err != nil {
panic(err)
if err = ps.con.HSet(ps.ctx, ctx, p.Key, p.Value).Err(); err != nil {
break
}
}
} else {
panic(err)
}
}
}
return
}
func (ps *store) Load(ctx string, key any) any {
v, err := ps.con.HGet(ps.ctx, ctx, key).Result()
err = asNil(err)
if err != nil {
panic(err)
func (ps *store) Load(ctx string, key string) (v any, err error) {
v, err = ps.con.HGet(ps.ctx, ctx, key).Result()
if err != nil && errors.Is(err, redis.Nil) {
v, err = nil, nil
}
return v
return
}
func (ps *store) Delete(ctx string, keys ...any) {
func (ps *store) Delete(ctx string, keys ...string) (err error) {
if len(keys) > 0 {
err := asNil(ps.con.HDel(ps.ctx, keys...).Err())
err = asNil(ps.con.HDel(ps.ctx, ctx, keys...).Err())
if err != nil {
if strings.Contains(err.Error(), argNumErrorMsg) {
log.Warn("This REDIS version/implementation does not support variadic arguments for HDEL")
for _, k := range keys {
if err := asNil(ps.con.HDel(ps.ctx, ctx, k).Err()); err != nil {
panic(err)
if err = asNil(ps.con.HDel(ps.ctx, ctx, k).Err()); err != nil {
break
}
}
} else {
panic(err)
}
}
}
return
}
// collectGarbage deletes all Peers from the Storage which are older than the
@@ -751,108 +750,125 @@ func (ps *store) Delete(ctx string, keys ...any) {
// - If the change happens after the HLEN, we will not even attempt to make the
// transaction. The infohash key will remain in the addressFamil hash and
// we'll attempt to clean it up the next time collectGarbage runs.
func (ps *store) collectGarbage(cutoff time.Time) error {
func (ps *store) collectGarbage(cutoff time.Time) {
cutoffUnix := cutoff.UnixNano()
start := time.Now()
var err error
for _, group := range groups {
// list all infohashes in the group
infohashesList, err := ps.con.HKeys(ps.ctx, group).Result()
// list all infoHashes in the group
var infoHashes []string
infoHashes, err = ps.con.HKeys(ps.ctx, group).Result()
err = asNil(err)
if err != nil {
return err
}
for _, ihStr := range infohashesList {
isSeeder := len(ihStr) > 5 && ihStr[5:6] == "S"
// list all (peer, timeout) pairs for the ih
ihList, err := ps.con.HGetAll(ps.ctx, ihStr).Result()
err = asNil(err)
if err != nil {
return err
}
var removedPeerCount int64
for k, v := range ihList {
peerKey := storage.SerializedPeer(k)
mtime, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return err
}
if mtime <= cutoffUnix {
log.Debug("storage: deleting peer", log.Fields{
"Peer": peerKey.ToPeer(),
})
ret, err := ps.con.HDel(ps.ctx, ihStr, peerKey.String()).Result()
if err != nil {
return err
if err == nil {
for _, infoHash := range infoHashes {
isSeeder := len(infoHash) > 5 && infoHash[5:6] == "S"
// list all (peer, timeout) pairs for the ih
peerList, err := ps.con.HGetAll(ps.ctx, infoHash).Result()
err = asNil(err)
if err == nil {
var removedPeerCount int64
for peerKey, timeStamp := range peerList {
var peer bittorrent.Peer
if peer, err = bittorrent.NewPeer(peerKey); err == nil {
if mtime, err := strconv.ParseInt(timeStamp, 10, 64); err == nil {
if mtime <= cutoffUnix {
log.Debug("storage: deleting peer", log.Fields{
"Peer": peer,
})
var count int64
count, err = ps.con.HDel(ps.ctx, infoHash, peerKey).Result()
err = asNil(err)
if err == nil {
removedPeerCount += count
}
}
}
}
if err != nil {
log.Error("storage: Redis: unable to delete info hash peer", log.Fields{
"group": group,
"infoHash": infoHash,
"peer": peer,
"key": peerKey,
"error": err,
})
}
}
// DECR seeder/leecher counter
if removedPeerCount > 0 {
var decrCounter string
if isSeeder {
decrCounter = seederCountKey(group)
} else {
decrCounter = leecherCountKey(group)
}
if err := ps.con.DecrBy(ps.ctx, decrCounter, removedPeerCount).Err(); err != nil {
log.Error("storage: Redis: unable to decrement seeder/leecher peer count", log.Fields{
"group": group,
"infoHash": infoHash,
"key": decrCounter,
"error": err,
})
}
}
removedPeerCount += ret
}
}
// DECR seeder/leecher counter
decrCounter := leecherCountKey(group)
if isSeeder {
decrCounter = seederCountKey(group)
}
if removedPeerCount > 0 {
if err := ps.con.DecrBy(ps.ctx, decrCounter, removedPeerCount).Err(); err != nil {
return err
}
}
// use WATCH to avoid race condition
// https://redis.io/topics/transactions
_, err = ps.con.Watch(ps.ctx, ihStr)
if err != nil {
return err
}
ihLen, err := ps.con.HLen(ps.ctx, ihStr).Result()
if err != nil {
return err
}
if ihLen == 0 {
// Empty hashes are not shown among existing keys,
// in other words, it's removed automatically after `HDEL` the last field.
// _, err := ps.con.Del(ps.ctx, ihStr)
_ = ps.con.Multi(ps.ctx)
_ = ps.con.HDel(ps.ctx, group, ihStr)
if isSeeder {
_ = ps.con.Decr(ps.ctx, infohashCountKey(group))
}
_, err = redis.Values(ps.con.Exec(ps.ctx))
if err != nil && !errors.Is(err, redis.Nil) {
log.Error("storage: Redis EXEC failure", log.Fields{
// use WATCH to avoid race condition
// https://redis.io/topics/transactions
err = asNil(ps.con.Watch(ps.ctx, func(tx *redis.Tx) (err error) {
var infoHashCount int64
infoHashCount, err = ps.con.HLen(ps.ctx, infoHash).Result()
err = asNil(err)
if err == nil && infoHashCount == 0 {
// Empty hashes are not shown among existing keys,
// in other words, it's removed automatically after `HDEL` the last field.
// _, err := ps.con.Del(ps.ctx, infoHash)
var deletedCount int64
deletedCount, err = ps.con.HDel(ps.ctx, group, infoHash).Result()
err = asNil(err)
if err == nil && isSeeder && deletedCount > 0 {
err = ps.con.Decr(ps.ctx, infoHashCountKey(group)).Err()
}
}
return err
}, infoHash))
if err != nil {
log.Error("storage: Redis: unable to clean info hash records", log.Fields{
"group": group,
"infoHash": infoHash,
"error": err,
})
}
} else {
log.Error("storage: Redis: unable to fetch info hash peers", log.Fields{
"group": group,
"infohash": ihStr,
"infoHash": infoHash,
"error": err,
})
}
} else {
if _, err = ps.con.Unwatch(ps.ctx); err != nil && !errors.Is(err, redis.Nil) {
log.Error("storage: Redis UNWATCH failure", log.Fields{"error": err})
}
}
} else {
log.Error("storage: Redis: unable to fetch info hashes", log.Fields{"group": group, "error": err})
}
}
duration := float64(time.Since(start).Nanoseconds()) / float64(time.Millisecond)
duration := time.Since(start).Milliseconds()
log.Debug("storage: recordGCDuration", log.Fields{"timeTaken(ms)": duration})
storage.PromGCDurationMilliseconds.Observe(duration)
return nil
storage.PromGCDurationMilliseconds.Observe(float64(duration))
}
func (ps *store) Stop() stop.Result {
c := make(stop.Channel)
go func() {
close(ps.closed)
if ps.closed != nil {
close(ps.closed)
}
ps.wg.Wait()
log.Info("storage: exiting. mochi does not clear data in redis when exiting. mochi keys have prefix 'IPv{4,6}_'.")
c.Done()
var err error
if ps.con != nil {
log.Info("storage: exiting. mochi does not clear data in redis when exiting. mochi keys have prefix 'IPv{4,6}_'.")
err = ps.con.Close()
}
c.Done(err)
}()
return c.Result()
+20 -12
View File
@@ -11,21 +11,29 @@ import (
"github.com/sot-tech/mochi/storage/test"
)
var conf = Config{
GarbageCollectionInterval: 10 * time.Minute,
PrometheusReportingInterval: 10 * time.Minute,
PeerLifetime: 30 * time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
ConnectTimeout: 10 * time.Second,
}
func createNew() s.Storage {
rs, err := miniredis.Run()
var ps s.Storage
var err error
ps, err = New(conf)
if err != nil {
panic(err)
fmt.Println("unable to create real Redis connection: ", err, " using simulator")
var rs *miniredis.Miniredis
rs, err = miniredis.Run()
if err != nil {
panic(err)
}
conf.Addresses = []string{rs.Addr()}
ps, err = New(conf)
}
redisURL := fmt.Sprintf("redis://@%s/0", rs.Addr())
ps, err := New(Config{
GarbageCollectionInterval: 10 * time.Minute,
PrometheusReportingInterval: 10 * time.Minute,
PeerLifetime: 30 * time.Minute,
RedisBroker: redisURL,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
ConnectTimeout: 10 * time.Second,
})
if err != nil {
panic(err)
}
+11 -5
View File
@@ -14,6 +14,12 @@ var (
drivers = make(map[string]Driver)
)
// Entry - some key-value pair, used for BulkPut
type Entry struct {
Key string
Value any
}
// Driver is the interface used to initialize a new type of Storage.
type Driver interface {
NewStorage(cfg any) (Storage, error)
@@ -107,20 +113,20 @@ type Storage interface {
// Put used to place arbitrary k-v data with specified context
// into storage. ctx parameter used to group data
// (i.e. data only for specific middleware module)
Put(ctx string, key, value any)
Put(ctx string, value Entry) error
// BulkPut used to place array of k-v data in specified context.
// Useful when several data entries should be added in single transaction/connection
BulkPut(ctx string, pairs ...Pair)
BulkPut(ctx string, pairs ...Entry) error
// Contains checks if any data in specified context exist
Contains(ctx string, key any) bool
Contains(ctx string, key string) (bool, error)
// Load used to get arbitrary data in specified context by its key
Load(ctx string, key any) any
Load(ctx string, key string) (any, error)
// Delete used to delete arbitrary data in specified context by its keys
Delete(ctx string, keys ...any)
Delete(ctx string, keys ...string) error
// Stopper is an interface that expects a Stop method to stop the Storage.
// For more details see the documentation in the stop package.
+56 -56
View File
@@ -17,7 +17,7 @@ type benchData struct {
peers [1000]bittorrent.Peer
}
func generateInfohashes() (a [1000]bittorrent.InfoHash) {
func generateInfoHashes() (a [1000]bittorrent.InfoHash) {
for i := range a {
b := make([]byte, bittorrent.InfoHashV1Len)
rand.Read(b)
@@ -63,7 +63,7 @@ type benchHolder struct {
func (bh *benchHolder) runBenchmark(b *testing.B, parallel bool, sf benchSetupFunc, ef benchExecFunc) {
ps := bh.st()
bd := &benchData{generateInfohashes(), generatePeers()}
bd := &benchData{generateInfoHashes(), generatePeers()}
spacing := int32(1000 / runtime.NumCPU())
if sf != nil {
err := sf(ps, bd)
@@ -134,21 +134,21 @@ func (bh *benchHolder) Put1k(b *testing.B) {
})
}
// Put1kInfohash benchmarks the PutSeeder method of a storage.Storage by cycling
// Put1kInfoHash benchmarks the PutSeeder method of a storage.Storage by cycling
// through 1000 infohashes and putting the same peer into their swarms.
//
// Put1kInfohash can run in parallel.
func (bh *benchHolder) Put1kInfohash(b *testing.B) {
// Put1kInfoHash can run in parallel.
func (bh *benchHolder) Put1kInfoHash(b *testing.B) {
bh.runBenchmark(b, true, nil, func(i int, ps storage.Storage, bd *benchData) error {
return ps.PutSeeder(bd.infohashes[i%1000], bd.peers[0])
})
}
// Put1kInfohash1k benchmarks the PutSeeder method of a storage.Storage by cycling
// Put1kInfoHash1k benchmarks the PutSeeder method of a storage.Storage by cycling
// through 1000 infohashes and 1000 Peers and calling Put with them.
//
// Put1kInfohash1k can run in parallel.
func (bh *benchHolder) Put1kInfohash1k(b *testing.B) {
// Put1kInfoHash1k can run in parallel.
func (bh *benchHolder) Put1kInfoHash1k(b *testing.B) {
bh.runBenchmark(b, true, nil, func(i int, ps storage.Storage, bd *benchData) error {
err := ps.PutSeeder(bd.infohashes[i%1000], bd.peers[(i*3)%1000])
return err
@@ -183,11 +183,11 @@ func (bh *benchHolder) PutDelete1k(b *testing.B) {
})
}
// PutDelete1kInfohash behaves like PutDelete1k with 1000 infohashes instead of
// PutDelete1kInfoHash behaves like PutDelete1k with 1000 infohashes instead of
// 1000 Peers.
//
// PutDelete1kInfohash can not run in parallel.
func (bh *benchHolder) PutDelete1kInfohash(b *testing.B) {
// PutDelete1kInfoHash can not run in parallel.
func (bh *benchHolder) PutDelete1kInfoHash(b *testing.B) {
bh.runBenchmark(b, false, nil, func(i int, ps storage.Storage, bd *benchData) error {
err := ps.PutSeeder(bd.infohashes[i%1000], bd.peers[0])
if err != nil {
@@ -197,11 +197,11 @@ func (bh *benchHolder) PutDelete1kInfohash(b *testing.B) {
})
}
// PutDelete1kInfohash1k behaves like PutDelete1k with 1000 infohashes in
// PutDelete1kInfoHash1k behaves like PutDelete1k with 1000 infohashes in
// addition to 1000 Peers.
//
// PutDelete1kInfohash1k can not run in parallel.
func (bh *benchHolder) PutDelete1kInfohash1k(b *testing.B) {
// PutDelete1kInfoHash1k can not run in parallel.
func (bh *benchHolder) PutDelete1kInfoHash1k(b *testing.B) {
bh.runBenchmark(b, false, nil, func(i int, ps storage.Storage, bd *benchData) error {
err := ps.PutSeeder(bd.infohashes[i%1000], bd.peers[(i*3)%1000])
if err != nil {
@@ -234,22 +234,22 @@ func (bh *benchHolder) DeleteNonexist1k(b *testing.B) {
})
}
// DeleteNonexist1kInfohash benchmarks the DeleteSeeder method of a storage.Storage by
// DeleteNonexist1kInfoHash benchmarks the DeleteSeeder method of a storage.Storage by
// attempting to delete one Peer from one of 1000 infohashes.
//
// DeleteNonexist1kInfohash can run in parallel.
func (bh *benchHolder) DeleteNonexist1kInfohash(b *testing.B) {
// DeleteNonexist1kInfoHash can run in parallel.
func (bh *benchHolder) DeleteNonexist1kInfoHash(b *testing.B) {
bh.runBenchmark(b, true, nil, func(i int, ps storage.Storage, bd *benchData) error {
_ = ps.DeleteSeeder(bd.infohashes[i%1000], bd.peers[0])
return nil
})
}
// DeleteNonexist1kInfohash1k benchmarks the Delete method of a storage.Storage by
// attempting to delete one of 1000 Peers from one of 1000 Infohashes.
// DeleteNonexist1kInfoHash1k benchmarks the Delete method of a storage.Storage by
// attempting to delete one of 1000 Peers from one of 1000 InfoHashes.
//
// DeleteNonexist1kInfohash1k can run in parallel.
func (bh *benchHolder) DeleteNonexist1kInfohash1k(b *testing.B) {
// DeleteNonexist1kInfoHash1k can run in parallel.
func (bh *benchHolder) DeleteNonexist1kInfoHash1k(b *testing.B) {
bh.runBenchmark(b, true, nil, func(i int, ps storage.Storage, bd *benchData) error {
_ = ps.DeleteSeeder(bd.infohashes[i%1000], bd.peers[(i*3)%1000])
return nil
@@ -278,23 +278,23 @@ func (bh *benchHolder) GradNonexist1k(b *testing.B) {
})
}
// GradNonexist1kInfohash benchmarks the GraduateLeecher method of a storage.Storage
// by attempting to graduate a nonexistent Peer for one of 100 Infohashes.
// GradNonexist1kInfoHash benchmarks the GraduateLeecher method of a storage.Storage
// by attempting to graduate a nonexistent Peer for one of 100 InfoHashes.
//
// GradNonexist1kInfohash can run in parallel.
func (bh *benchHolder) GradNonexist1kInfohash(b *testing.B) {
// GradNonexist1kInfoHash can run in parallel.
func (bh *benchHolder) GradNonexist1kInfoHash(b *testing.B) {
bh.runBenchmark(b, true, nil, func(i int, ps storage.Storage, bd *benchData) error {
_ = ps.GraduateLeecher(bd.infohashes[i%1000], bd.peers[0])
return nil
})
}
// GradNonexist1kInfohash1k benchmarks the GraduateLeecher method of a storage.Storage
// GradNonexist1kInfoHash1k benchmarks the GraduateLeecher method of a storage.Storage
// by attempting to graduate one of 1000 nonexistent Peers for one of 1000
// infohashes.
//
// GradNonexist1kInfohash1k can run in parallel.
func (bh *benchHolder) GradNonexist1kInfohash1k(b *testing.B) {
// GradNonexist1kInfoHash1k can run in parallel.
func (bh *benchHolder) GradNonexist1kInfoHash1k(b *testing.B) {
bh.runBenchmark(b, true, nil, func(i int, ps storage.Storage, bd *benchData) error {
_ = ps.GraduateLeecher(bd.infohashes[i%1000], bd.peers[(i*3)%1000])
return nil
@@ -337,11 +337,11 @@ func (bh *benchHolder) PutGradDelete1k(b *testing.B) {
})
}
// PutGradDelete1kInfohash behaves like PutGradDelete with one of 1000
// PutGradDelete1kInfoHash behaves like PutGradDelete with one of 1000
// infohashes.
//
// PutGradDelete1kInfohash can not run in parallel.
func (bh *benchHolder) PutGradDelete1kInfohash(b *testing.B) {
// PutGradDelete1kInfoHash can not run in parallel.
func (bh *benchHolder) PutGradDelete1kInfoHash(b *testing.B) {
bh.runBenchmark(b, false, nil, func(i int, ps storage.Storage, bd *benchData) error {
err := ps.PutLeecher(bd.infohashes[i%1000], bd.peers[0])
if err != nil {
@@ -355,11 +355,11 @@ func (bh *benchHolder) PutGradDelete1kInfohash(b *testing.B) {
})
}
// PutGradDelete1kInfohash1k behaves like PutGradDelete with one of 1000 Peers
// PutGradDelete1kInfoHash1k behaves like PutGradDelete with one of 1000 Peers
// and one of 1000 infohashes.
//
// PutGradDelete1kInfohash can not run in parallel.
func (bh *benchHolder) PutGradDelete1kInfohash1k(b *testing.B) {
// PutGradDelete1kInfoHash can not run in parallel.
func (bh *benchHolder) PutGradDelete1kInfoHash1k(b *testing.B) {
bh.runBenchmark(b, false, nil, func(i int, ps storage.Storage, bd *benchData) error {
err := ps.PutLeecher(bd.infohashes[i%1000], bd.peers[(i*3)%1000])
if err != nil {
@@ -403,11 +403,11 @@ func (bh *benchHolder) AnnounceLeecher(b *testing.B) {
})
}
// AnnounceLeecher1kInfohash behaves like AnnounceLeecher with one of 1000
// AnnounceLeecher1kInfoHash behaves like AnnounceLeecher with one of 1000
// infohashes.
//
// AnnounceLeecher1kInfohash can run in parallel.
func (bh *benchHolder) AnnounceLeecher1kInfohash(b *testing.B) {
// AnnounceLeecher1kInfoHash can run in parallel.
func (bh *benchHolder) AnnounceLeecher1kInfoHash(b *testing.B) {
bh.runBenchmark(b, true, putPeers, func(i int, ps storage.Storage, bd *benchData) error {
_, err := ps.AnnouncePeers(bd.infohashes[i%1000], false, 50, bd.peers[0])
return err
@@ -425,11 +425,11 @@ func (bh *benchHolder) AnnounceSeeder(b *testing.B) {
})
}
// AnnounceSeeder1kInfohash behaves like AnnounceSeeder with one of 1000
// AnnounceSeeder1kInfoHash behaves like AnnounceSeeder with one of 1000
// infohashes.
//
// AnnounceSeeder1kInfohash can run in parallel.
func (bh *benchHolder) AnnounceSeeder1kInfohash(b *testing.B) {
// AnnounceSeeder1kInfoHash can run in parallel.
func (bh *benchHolder) AnnounceSeeder1kInfoHash(b *testing.B) {
bh.runBenchmark(b, true, putPeers, func(i int, ps storage.Storage, bd *benchData) error {
_, err := ps.AnnouncePeers(bd.infohashes[i%1000], true, 50, bd.peers[0])
return err
@@ -447,10 +447,10 @@ func (bh *benchHolder) ScrapeSwarm(b *testing.B) {
})
}
// ScrapeSwarm1kInfohash behaves like ScrapeSwarm with one of 1000 infohashes.
// ScrapeSwarm1kInfoHash behaves like ScrapeSwarm with one of 1000 infohashes.
//
// ScrapeSwarm1kInfohash can run in parallel.
func (bh *benchHolder) ScrapeSwarm1kInfohash(b *testing.B) {
// ScrapeSwarm1kInfoHash can run in parallel.
func (bh *benchHolder) ScrapeSwarm1kInfoHash(b *testing.B) {
bh.runBenchmark(b, true, putPeers, func(i int, ps storage.Storage, bd *benchData) error {
ps.ScrapeSwarm(bd.infohashes[i%1000], bittorrent.IPv4)
return nil
@@ -463,28 +463,28 @@ func RunBenchmarks(b *testing.B, newStorage benchStorageConstructor) {
b.Run("BenchmarkNop", bh.Nop)
b.Run("BenchmarkPut", bh.Put)
b.Run("BenchmarkPut1k", bh.Put1k)
b.Run("BenchmarkPut1kInfohash", bh.Put1kInfohash)
b.Run("BenchmarkPut1kInfohash1k", bh.Put1kInfohash1k)
b.Run("BenchmarkPut1kInfoHash", bh.Put1kInfoHash)
b.Run("BenchmarkPut1kInfoHash1k", bh.Put1kInfoHash1k)
b.Run("BenchmarkPutDelete", bh.PutDelete)
b.Run("BenchmarkPutDelete1k", bh.PutDelete1k)
b.Run("BenchmarkPutDelete1kInfohash", bh.PutDelete1kInfohash)
b.Run("BenchmarkPutDelete1kInfohash1k", bh.PutDelete1kInfohash1k)
b.Run("BenchmarkPutDelete1kInfoHash", bh.PutDelete1kInfoHash)
b.Run("BenchmarkPutDelete1kInfoHash1k", bh.PutDelete1kInfoHash1k)
b.Run("BenchmarkDeleteNonexist", bh.DeleteNonexist)
b.Run("BenchmarkDeleteNonexist1k", bh.DeleteNonexist1k)
b.Run("BenchmarkDeleteNonexist1kInfohash", bh.DeleteNonexist1kInfohash)
b.Run("BenchmarkDeleteNonexist1kInfohash1k", bh.DeleteNonexist1kInfohash1k)
b.Run("BenchmarkDeleteNonexist1kInfoHash", bh.DeleteNonexist1kInfoHash)
b.Run("BenchmarkDeleteNonexist1kInfoHash1k", bh.DeleteNonexist1kInfoHash1k)
b.Run("BenchmarkPutGradDelete", bh.PutGradDelete)
b.Run("BenchmarkPutGradDelete1k", bh.PutGradDelete1k)
b.Run("BenchmarkPutGradDelete1kInfohash", bh.PutGradDelete1kInfohash)
b.Run("BenchmarkPutGradDelete1kInfohash1k", bh.PutGradDelete1kInfohash1k)
b.Run("BenchmarkPutGradDelete1kInfoHash", bh.PutGradDelete1kInfoHash)
b.Run("BenchmarkPutGradDelete1kInfoHash1k", bh.PutGradDelete1kInfoHash1k)
b.Run("BenchmarkGradNonexist", bh.GradNonexist)
b.Run("BenchmarkGradNonexist1k", bh.GradNonexist1k)
b.Run("BenchmarkGradNonexist1kInfohash", bh.GradNonexist1kInfohash)
b.Run("BenchmarkGradNonexist1kInfohash1k", bh.GradNonexist1kInfohash1k)
b.Run("BenchmarkGradNonexist1kInfoHash", bh.GradNonexist1kInfoHash)
b.Run("BenchmarkGradNonexist1kInfoHash1k", bh.GradNonexist1kInfoHash1k)
b.Run("BenchmarkAnnounceLeecher", bh.AnnounceLeecher)
b.Run("BenchmarkAnnounceLeecher1kInfohash", bh.AnnounceLeecher1kInfohash)
b.Run("BenchmarkAnnounceLeecher1kInfoHash", bh.AnnounceLeecher1kInfoHash)
b.Run("BenchmarkAnnounceSeeder", bh.AnnounceSeeder)
b.Run("BenchmarkAnnounceSeeder1kInfohash", bh.AnnounceSeeder1kInfohash)
b.Run("BenchmarkAnnounceSeeder1kInfoHash", bh.AnnounceSeeder1kInfoHash)
b.Run("BenchmarkScrapeSwarm", bh.ScrapeSwarm)
b.Run("BenchmarkScrapeSwarm1kInfohash", bh.ScrapeSwarm1kInfohash)
b.Run("BenchmarkScrapeSwarm1kInfoHash", bh.ScrapeSwarm1kInfoHash)
}
+29 -18
View File
@@ -171,64 +171,75 @@ func (th *testHolder) LeecherPutGraduateAnnounceDeleteAnnounce(t *testing.T) {
func (th *testHolder) CustomPutContainsLoadDelete(t *testing.T) {
for _, c := range testData {
th.st.Put("test", c.peer.String(), c.ih.RawString())
err := th.st.Put("test", storage.Entry{Key: c.peer.String(), Value: c.ih.RawString()})
require.Nil(t, err)
// check if exist in ctx we put
contains := th.st.Contains("test", c.peer.String())
contains, err := th.st.Contains("test", c.peer.String())
require.Nil(t, err)
require.True(t, contains)
// check if not exist in another ctx
contains = th.st.Contains("", c.peer.String())
contains, err = th.st.Contains("", c.peer.String())
require.Nil(t, err)
require.False(t, contains)
// check value and type in ctx we put
out := th.st.Load("test", c.peer.String())
out, err := th.st.Load("test", c.peer.String())
require.Nil(t, err)
ih, err := bittorrent.NewInfoHash(out)
require.Nil(t, err)
require.Equal(t, c.ih, ih)
// check value is nil in another ctx
dummy := th.st.Load("", c.peer.String())
dummy, err := th.st.Load("", c.peer.String())
require.Nil(t, err)
require.Nil(t, dummy)
th.st.Delete("test", c.peer.String())
err = th.st.Delete("test", c.peer.String())
require.Nil(t, err)
contains = th.st.Contains("peers", c.peer.String())
contains, err = th.st.Contains("peers", c.peer.String())
require.Nil(t, err)
require.False(t, contains)
}
}
func (th *testHolder) CustomBulkPutContainsLoadDelete(t *testing.T) {
pairs := make([]storage.Pair, 0, len(testData))
keys := make([]any, 0, len(testData))
pairs := make([]storage.Entry, 0, len(testData))
keys := make([]string, 0, len(testData))
for _, c := range testData {
key := c.peer.String()
keys = append(keys, key)
pairs = append(pairs, storage.Pair{
Left: key,
Right: c.ih.RawString(),
pairs = append(pairs, storage.Entry{
Key: key,
Value: c.ih.RawString(),
})
}
th.st.BulkPut("test", pairs...)
err := th.st.BulkPut("test", pairs...)
require.Nil(t, err)
// check if exist in ctx we put
for _, k := range keys {
contains := th.st.Contains("test", k)
contains, err := th.st.Contains("test", k)
require.Nil(t, err)
require.True(t, contains)
}
// check value and type in ctx we put
for _, p := range pairs {
out := th.st.Load("test", p.Left)
out, _ := th.st.Load("test", p.Key)
ih, err := bittorrent.NewInfoHash(out)
require.Nil(t, err)
require.Equal(t, p.Right, ih.RawString())
require.Equal(t, p.Value, ih.RawString())
}
th.st.Delete("test", keys...)
err = th.st.Delete("test", keys...)
require.Nil(t, err)
for _, k := range keys {
contains := th.st.Contains("test", k)
contains, err := th.st.Contains("test", k)
require.Nil(t, err)
require.False(t, contains)
}
}