|
@@ -25,45 +25,32 @@ import (
|
|
|
"errors"
|
|
"errors"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"math/rand"
|
|
"math/rand"
|
|
|
- "os"
|
|
|
|
|
- "path/filepath"
|
|
|
|
|
"sync"
|
|
"sync"
|
|
|
- "time"
|
|
|
|
|
|
|
|
|
|
- "github.com/Psiphon-Labs/bolt"
|
|
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-const (
|
|
|
|
|
- serverEntriesBucket = "serverEntries"
|
|
|
|
|
- rankedServerEntriesBucket = "rankedServerEntries"
|
|
|
|
|
- rankedServerEntriesKey = "rankedServerEntries"
|
|
|
|
|
- splitTunnelRouteETagsBucket = "splitTunnelRouteETags"
|
|
|
|
|
- splitTunnelRouteDataBucket = "splitTunnelRouteData"
|
|
|
|
|
- urlETagsBucket = "urlETags"
|
|
|
|
|
- keyValueBucket = "keyValues"
|
|
|
|
|
- tunnelStatsBucket = "tunnelStats"
|
|
|
|
|
- remoteServerListStatsBucket = "remoteServerListStats"
|
|
|
|
|
- slokBucket = "SLOKs"
|
|
|
|
|
- tacticsBucket = "tactics"
|
|
|
|
|
- speedTestSamplesBucket = "speedTestSamples"
|
|
|
|
|
-
|
|
|
|
|
- rankedServerEntryCount = 100
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-const (
|
|
|
|
|
- DATA_STORE_FILENAME = "psiphon.boltdb"
|
|
|
|
|
- DATA_STORE_LAST_CONNECTED_KEY = "lastConnected"
|
|
|
|
|
- DATA_STORE_LAST_SERVER_ENTRY_FILTER_KEY = "lastServerEntryFilter"
|
|
|
|
|
- PERSISTENT_STAT_TYPE_REMOTE_SERVER_LIST = remoteServerListStatsBucket
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
var (
|
|
var (
|
|
|
|
|
+ datastoreServerEntriesBucket = []byte("serverEntries")
|
|
|
|
|
+ datastoreSplitTunnelRouteETagsBucket = []byte("splitTunnelRouteETags")
|
|
|
|
|
+ datastoreSplitTunnelRouteDataBucket = []byte("splitTunnelRouteData")
|
|
|
|
|
+ datastoreUrlETagsBucket = []byte("urlETags")
|
|
|
|
|
+ datastoreKeyValueBucket = []byte("keyValues")
|
|
|
|
|
+ datastoreRemoteServerListStatsBucket = []byte("remoteServerListStats")
|
|
|
|
|
+ datastoreSLOKsBucket = []byte("SLOKs")
|
|
|
|
|
+ datastoreTacticsBucket = []byte("tactics")
|
|
|
|
|
+ datastoreSpeedTestSamplesBucket = []byte("speedTestSamples")
|
|
|
|
|
+ datastoreLastConnectedKey = "lastConnected"
|
|
|
|
|
+ datastoreLastServerEntryFilterKey = []byte("lastServerEntryFilter")
|
|
|
|
|
+ datastoreAffinityServerEntryIDKey = []byte("affinityServerEntryID")
|
|
|
|
|
+ datastorePersistentStatTypeRemoteServerList = string(datastoreRemoteServerListStatsBucket)
|
|
|
|
|
+ datastoreServerEntryFetchGCThreshold = 20
|
|
|
|
|
+
|
|
|
datastoreInitalizeMutex sync.Mutex
|
|
datastoreInitalizeMutex sync.Mutex
|
|
|
datastoreReferenceMutex sync.Mutex
|
|
datastoreReferenceMutex sync.Mutex
|
|
|
- datastoreDB *bolt.DB
|
|
|
|
|
|
|
+ activeDatastoreDB *datastoreDB
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// OpenDataStore opens and initializes the singleton data store instance.
|
|
// OpenDataStore opens and initializes the singleton data store instance.
|
|
@@ -73,99 +60,20 @@ func OpenDataStore(config *Config) error {
|
|
|
defer datastoreInitalizeMutex.Unlock()
|
|
defer datastoreInitalizeMutex.Unlock()
|
|
|
|
|
|
|
|
datastoreReferenceMutex.Lock()
|
|
datastoreReferenceMutex.Lock()
|
|
|
- existingDB := datastoreDB
|
|
|
|
|
|
|
+ existingDB := activeDatastoreDB
|
|
|
datastoreReferenceMutex.Unlock()
|
|
datastoreReferenceMutex.Unlock()
|
|
|
|
|
|
|
|
if existingDB != nil {
|
|
if existingDB != nil {
|
|
|
return common.ContextError(errors.New("db already open"))
|
|
return common.ContextError(errors.New("db already open"))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- filename := filepath.Join(config.DataStoreDirectory, DATA_STORE_FILENAME)
|
|
|
|
|
-
|
|
|
|
|
- var newDB *bolt.DB
|
|
|
|
|
- var err error
|
|
|
|
|
-
|
|
|
|
|
- for retry := 0; retry < 3; retry++ {
|
|
|
|
|
-
|
|
|
|
|
- if retry > 0 {
|
|
|
|
|
- NoticeAlert("OpenDataStore retry: %d", retry)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- newDB, err = bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
|
|
|
-
|
|
|
|
|
- // The datastore file may be corrupt, so attempt to delete and try again
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- NoticeAlert("bolt.Open error: %s", err)
|
|
|
|
|
- os.Remove(filename)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Run consistency checks on datastore and emit errors for diagnostics purposes
|
|
|
|
|
- // We assume this will complete quickly for typical size Psiphon datastores.
|
|
|
|
|
- err = newDB.View(func(tx *bolt.Tx) error {
|
|
|
|
|
- return tx.SynchronousCheck()
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- // The datastore file may be corrupt, so attempt to delete and try again
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- NoticeAlert("bolt.SynchronousCheck error: %s", err)
|
|
|
|
|
- newDB.Close()
|
|
|
|
|
- os.Remove(filename)
|
|
|
|
|
- continue
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- break
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(fmt.Errorf("failed to open database: %s", err))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- err = newDB.Update(func(tx *bolt.Tx) error {
|
|
|
|
|
- requiredBuckets := []string{
|
|
|
|
|
- serverEntriesBucket,
|
|
|
|
|
- rankedServerEntriesBucket,
|
|
|
|
|
- splitTunnelRouteETagsBucket,
|
|
|
|
|
- splitTunnelRouteDataBucket,
|
|
|
|
|
- urlETagsBucket,
|
|
|
|
|
- keyValueBucket,
|
|
|
|
|
- tunnelStatsBucket,
|
|
|
|
|
- remoteServerListStatsBucket,
|
|
|
|
|
- slokBucket,
|
|
|
|
|
- tacticsBucket,
|
|
|
|
|
- speedTestSamplesBucket,
|
|
|
|
|
- }
|
|
|
|
|
- for _, bucket := range requiredBuckets {
|
|
|
|
|
- _, err := tx.CreateBucketIfNotExists([]byte(bucket))
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return nil
|
|
|
|
|
- })
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(fmt.Errorf("failed to create buckets: %s", err))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Cleanup obsolete tunnel (session) stats bucket, if one still exists
|
|
|
|
|
-
|
|
|
|
|
- err = newDB.Update(func(tx *bolt.Tx) error {
|
|
|
|
|
- tunnelStatsBucket := []byte("tunnelStats")
|
|
|
|
|
- if tx.Bucket(tunnelStatsBucket) != nil {
|
|
|
|
|
- err := tx.DeleteBucket(tunnelStatsBucket)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- NoticeAlert("DeleteBucket %s error: %s", tunnelStatsBucket, err)
|
|
|
|
|
- // Continue, since this is not fatal
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return nil
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ newDB, err := datastoreOpenDB(config.DataStoreDirectory)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return common.ContextError(fmt.Errorf("failed to create buckets: %s", err))
|
|
|
|
|
|
|
+ return common.ContextError(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
datastoreReferenceMutex.Lock()
|
|
datastoreReferenceMutex.Lock()
|
|
|
- datastoreDB = newDB
|
|
|
|
|
|
|
+ activeDatastoreDB = newDB
|
|
|
datastoreReferenceMutex.Unlock()
|
|
datastoreReferenceMutex.Unlock()
|
|
|
|
|
|
|
|
_ = resetAllPersistentStatsToUnreported()
|
|
_ = resetAllPersistentStatsToUnreported()
|
|
@@ -182,49 +90,53 @@ func CloseDataStore() {
|
|
|
datastoreReferenceMutex.Lock()
|
|
datastoreReferenceMutex.Lock()
|
|
|
defer datastoreReferenceMutex.Unlock()
|
|
defer datastoreReferenceMutex.Unlock()
|
|
|
|
|
|
|
|
- if datastoreDB == nil {
|
|
|
|
|
|
|
+ if activeDatastoreDB == nil {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- err := datastoreDB.Close()
|
|
|
|
|
|
|
+ err := activeDatastoreDB.close()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- NoticeAlert("failed to close database: %s", err)
|
|
|
|
|
|
|
+ NoticeAlert("failed to close database: %s", common.ContextError(err))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- datastoreDB = nil
|
|
|
|
|
|
|
+ activeDatastoreDB = nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func dataStoreView(fn func(tx *bolt.Tx) error) error {
|
|
|
|
|
|
|
+func datastoreView(fn func(tx *datastoreTx) error) error {
|
|
|
|
|
|
|
|
datastoreReferenceMutex.Lock()
|
|
datastoreReferenceMutex.Lock()
|
|
|
- db := datastoreDB
|
|
|
|
|
|
|
+ db := activeDatastoreDB
|
|
|
datastoreReferenceMutex.Unlock()
|
|
datastoreReferenceMutex.Unlock()
|
|
|
|
|
|
|
|
if db == nil {
|
|
if db == nil {
|
|
|
return common.ContextError(errors.New("database not open"))
|
|
return common.ContextError(errors.New("database not open"))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return db.View(fn)
|
|
|
|
|
|
|
+ err := db.view(fn)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ err = common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func dataStoreUpdate(fn func(tx *bolt.Tx) error) error {
|
|
|
|
|
|
|
+func datastoreUpdate(fn func(tx *datastoreTx) error) error {
|
|
|
|
|
|
|
|
datastoreReferenceMutex.Lock()
|
|
datastoreReferenceMutex.Lock()
|
|
|
- db := datastoreDB
|
|
|
|
|
|
|
+ db := activeDatastoreDB
|
|
|
datastoreReferenceMutex.Unlock()
|
|
datastoreReferenceMutex.Unlock()
|
|
|
|
|
|
|
|
if db == nil {
|
|
if db == nil {
|
|
|
return common.ContextError(errors.New("database not open"))
|
|
return common.ContextError(errors.New("database not open"))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return db.Update(fn)
|
|
|
|
|
|
|
+ err := db.update(fn)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ err = common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// StoreServerEntry adds the server entry to the data store.
|
|
// StoreServerEntry adds the server entry to the data store.
|
|
|
-// A newly stored (or re-stored) server entry is assigned the next-to-top
|
|
|
|
|
-// rank for iteration order (the previous top ranked entry is promoted). The
|
|
|
|
|
-// purpose of inserting at next-to-top is to keep the last selected server
|
|
|
|
|
-// as the top ranked server.
|
|
|
|
|
//
|
|
//
|
|
|
// When a server entry already exists for a given server, it will be
|
|
// When a server entry already exists for a given server, it will be
|
|
|
// replaced only if replaceIfExists is set or if the the ConfigurationVersion
|
|
// replaced only if replaceIfExists is set or if the the ConfigurationVersion
|
|
@@ -250,16 +162,16 @@ func StoreServerEntry(serverEntryFields protocol.ServerEntryFields, replaceIfExi
|
|
|
// values (e.g., many servers support all protocols), performance
|
|
// values (e.g., many servers support all protocols), performance
|
|
|
// is expected to be acceptable.
|
|
// is expected to be acceptable.
|
|
|
|
|
|
|
|
- err = dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err = datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
- serverEntries := tx.Bucket([]byte(serverEntriesBucket))
|
|
|
|
|
|
|
+ serverEntries := tx.bucket(datastoreServerEntriesBucket)
|
|
|
|
|
|
|
|
ipAddress := serverEntryFields.GetIPAddress()
|
|
ipAddress := serverEntryFields.GetIPAddress()
|
|
|
|
|
|
|
|
// Check not only that the entry exists, but is valid. This
|
|
// Check not only that the entry exists, but is valid. This
|
|
|
// will replace in the rare case where the data is corrupt.
|
|
// will replace in the rare case where the data is corrupt.
|
|
|
existingConfigurationVersion := -1
|
|
existingConfigurationVersion := -1
|
|
|
- existingData := serverEntries.Get([]byte(ipAddress))
|
|
|
|
|
|
|
+ existingData := serverEntries.get([]byte(ipAddress))
|
|
|
if existingData != nil {
|
|
if existingData != nil {
|
|
|
var existingServerEntry *protocol.ServerEntry
|
|
var existingServerEntry *protocol.ServerEntry
|
|
|
err := json.Unmarshal(existingData, &existingServerEntry)
|
|
err := json.Unmarshal(existingData, &existingServerEntry)
|
|
@@ -284,12 +196,7 @@ func StoreServerEntry(serverEntryFields protocol.ServerEntryFields, replaceIfExi
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return common.ContextError(err)
|
|
return common.ContextError(err)
|
|
|
}
|
|
}
|
|
|
- err = serverEntries.Put([]byte(ipAddress), data)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- err = insertRankedServerEntry(tx, ipAddress, 1)
|
|
|
|
|
|
|
+ err = serverEntries.put([]byte(ipAddress), data)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return common.ContextError(err)
|
|
return common.ContextError(err)
|
|
|
}
|
|
}
|
|
@@ -334,6 +241,7 @@ func StreamingStoreServerEntries(
|
|
|
// so this isn't true constant-memory streaming (it depends on garbage
|
|
// so this isn't true constant-memory streaming (it depends on garbage
|
|
|
// collection).
|
|
// collection).
|
|
|
|
|
|
|
|
|
|
+ n := 0
|
|
|
for {
|
|
for {
|
|
|
serverEntry, err := serverEntries.Next()
|
|
serverEntry, err := serverEntries.Next()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -349,22 +257,28 @@ func StreamingStoreServerEntries(
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return common.ContextError(err)
|
|
return common.ContextError(err)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ n += 1
|
|
|
|
|
+ if n == datastoreServerEntryFetchGCThreshold {
|
|
|
|
|
+ defaultGarbageCollection()
|
|
|
|
|
+ n = 0
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// PromoteServerEntry assigns the top rank (one more than current
|
|
|
|
|
-// max rank) to the specified server entry. Server candidates are
|
|
|
|
|
-// iterated in decending rank order, so this server entry will be
|
|
|
|
|
-// the first candidate in a subsequent tunnel establishment.
|
|
|
|
|
|
|
+// PromoteServerEntry sets the server affinity server entry ID to the
|
|
|
|
|
+// specified server entry IP address.
|
|
|
func PromoteServerEntry(config *Config, ipAddress string) error {
|
|
func PromoteServerEntry(config *Config, ipAddress string) error {
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+
|
|
|
|
|
+ serverEntryID := []byte(ipAddress)
|
|
|
|
|
|
|
|
- // Ensure the corresponding entry exists before
|
|
|
|
|
- // inserting into rank.
|
|
|
|
|
- bucket := tx.Bucket([]byte(serverEntriesBucket))
|
|
|
|
|
- data := bucket.Get([]byte(ipAddress))
|
|
|
|
|
|
|
+ // Ensure the corresponding server entry exists before
|
|
|
|
|
+ // setting server affinity.
|
|
|
|
|
+ bucket := tx.bucket(datastoreServerEntriesBucket)
|
|
|
|
|
+ data := bucket.get(serverEntryID)
|
|
|
if data == nil {
|
|
if data == nil {
|
|
|
NoticeAlert(
|
|
NoticeAlert(
|
|
|
"PromoteServerEntry: ignoring unknown server entry: %s",
|
|
"PromoteServerEntry: ignoring unknown server entry: %s",
|
|
@@ -372,7 +286,8 @@ func PromoteServerEntry(config *Config, ipAddress string) error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- err := insertRankedServerEntry(tx, ipAddress, 0)
|
|
|
|
|
|
|
+ bucket = tx.bucket(datastoreKeyValueBucket)
|
|
|
|
|
+ err := bucket.put(datastoreAffinityServerEntryIDKey, serverEntryID)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -386,8 +301,8 @@ func PromoteServerEntry(config *Config, ipAddress string) error {
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
|
- bucket = tx.Bucket([]byte(keyValueBucket))
|
|
|
|
|
- return bucket.Put([]byte(DATA_STORE_LAST_SERVER_ENTRY_FILTER_KEY), currentFilter)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return bucket.put(datastoreLastServerEntryFilterKey, currentFilter)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -413,13 +328,13 @@ func hasServerEntryFilterChanged(config *Config) (bool, error) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
changed := false
|
|
changed := false
|
|
|
- err = dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err = datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
// previousFilter will be nil not found (not previously
|
|
// previousFilter will be nil not found (not previously
|
|
|
// set) which will never match any current filter.
|
|
// set) which will never match any current filter.
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(keyValueBucket))
|
|
|
|
|
- previousFilter := bucket.Get([]byte(DATA_STORE_LAST_SERVER_ENTRY_FILTER_KEY))
|
|
|
|
|
|
|
+ bucket := tx.bucket(datastoreKeyValueBucket)
|
|
|
|
|
+ previousFilter := bucket.get(datastoreLastServerEntryFilterKey)
|
|
|
if bytes.Compare(previousFilter, currentFilter) != 0 {
|
|
if bytes.Compare(previousFilter, currentFilter) != 0 {
|
|
|
changed = true
|
|
changed = true
|
|
|
}
|
|
}
|
|
@@ -432,87 +347,12 @@ func hasServerEntryFilterChanged(config *Config) (bool, error) {
|
|
|
return changed, nil
|
|
return changed, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func getRankedServerEntries(tx *bolt.Tx) ([]string, error) {
|
|
|
|
|
- bucket := tx.Bucket([]byte(rankedServerEntriesBucket))
|
|
|
|
|
- data := bucket.Get([]byte(rankedServerEntriesKey))
|
|
|
|
|
-
|
|
|
|
|
- if data == nil {
|
|
|
|
|
- return []string{}, nil
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- rankedServerEntries := make([]string, 0)
|
|
|
|
|
- err := json.Unmarshal(data, &rankedServerEntries)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
- return rankedServerEntries, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func setRankedServerEntries(tx *bolt.Tx, rankedServerEntries []string) error {
|
|
|
|
|
- data, err := json.Marshal(rankedServerEntries)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- bucket := tx.Bucket([]byte(rankedServerEntriesBucket))
|
|
|
|
|
- err = bucket.Put([]byte(rankedServerEntriesKey), data)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func insertRankedServerEntry(tx *bolt.Tx, serverEntryId string, position int) error {
|
|
|
|
|
- rankedServerEntries, err := getRankedServerEntries(tx)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // BoltDB implementation note:
|
|
|
|
|
- // For simplicity, we store the ranked server ids in an array serialized to
|
|
|
|
|
- // a single key value. To ensure this value doesn't grow without bound,
|
|
|
|
|
- // it's capped at rankedServerEntryCount. For now, this cap should be large
|
|
|
|
|
- // enough to meet the shuffleHeadLength = config.TunnelPoolSize criteria, for
|
|
|
|
|
- // any reasonable configuration of config.TunnelPoolSize.
|
|
|
|
|
-
|
|
|
|
|
- // Using: https://github.com/golang/go/wiki/SliceTricks
|
|
|
|
|
-
|
|
|
|
|
- // When serverEntryId is already ranked, remove it first to avoid duplicates
|
|
|
|
|
-
|
|
|
|
|
- for i, rankedServerEntryId := range rankedServerEntries {
|
|
|
|
|
- if rankedServerEntryId == serverEntryId {
|
|
|
|
|
- rankedServerEntries = append(
|
|
|
|
|
- rankedServerEntries[:i], rankedServerEntries[i+1:]...)
|
|
|
|
|
- break
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // SliceTricks insert, with length cap enforced
|
|
|
|
|
-
|
|
|
|
|
- if len(rankedServerEntries) < rankedServerEntryCount {
|
|
|
|
|
- rankedServerEntries = append(rankedServerEntries, "")
|
|
|
|
|
- }
|
|
|
|
|
- if position >= len(rankedServerEntries) {
|
|
|
|
|
- position = len(rankedServerEntries) - 1
|
|
|
|
|
- }
|
|
|
|
|
- copy(rankedServerEntries[position+1:], rankedServerEntries[position:])
|
|
|
|
|
- rankedServerEntries[position] = serverEntryId
|
|
|
|
|
-
|
|
|
|
|
- err = setRankedServerEntries(tx, rankedServerEntries)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// ServerEntryIterator is used to iterate over
|
|
// ServerEntryIterator is used to iterate over
|
|
|
// stored server entries in rank order.
|
|
// stored server entries in rank order.
|
|
|
type ServerEntryIterator struct {
|
|
type ServerEntryIterator struct {
|
|
|
config *Config
|
|
config *Config
|
|
|
- shuffleHeadLength int
|
|
|
|
|
- serverEntryIds []string
|
|
|
|
|
|
|
+ applyServerAffinity bool
|
|
|
|
|
+ serverEntryIDs [][]byte
|
|
|
serverEntryIndex int
|
|
serverEntryIndex int
|
|
|
isTacticsServerEntryIterator bool
|
|
isTacticsServerEntryIterator bool
|
|
|
isTargetServerEntryIterator bool
|
|
isTargetServerEntryIterator bool
|
|
@@ -547,8 +387,8 @@ func NewServerEntryIterator(config *Config) (bool, *ServerEntryIterator, error)
|
|
|
applyServerAffinity := !filterChanged
|
|
applyServerAffinity := !filterChanged
|
|
|
|
|
|
|
|
iterator := &ServerEntryIterator{
|
|
iterator := &ServerEntryIterator{
|
|
|
- config: config,
|
|
|
|
|
- shuffleHeadLength: config.TunnelPoolSize,
|
|
|
|
|
|
|
+ config: config,
|
|
|
|
|
+ applyServerAffinity: applyServerAffinity,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
err = iterator.Reset()
|
|
err = iterator.Reset()
|
|
@@ -568,7 +408,6 @@ func NewTacticsServerEntryIterator(config *Config) (*ServerEntryIterator, error)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
iterator := &ServerEntryIterator{
|
|
iterator := &ServerEntryIterator{
|
|
|
- shuffleHeadLength: 0,
|
|
|
|
|
isTacticsServerEntryIterator: true,
|
|
isTacticsServerEntryIterator: true,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -634,11 +473,6 @@ func (iterator *ServerEntryIterator) Reset() error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // This query implements the Psiphon server candidate selection
|
|
|
|
|
- // algorithm: the first TunnelPoolSize server candidates are in rank
|
|
|
|
|
- // (priority) order, to favor previously successful servers; then the
|
|
|
|
|
- // remaining long tail is shuffled to raise up less recent candidates.
|
|
|
|
|
-
|
|
|
|
|
// BoltDB implementation note:
|
|
// BoltDB implementation note:
|
|
|
// We don't keep a transaction open for the duration of the iterator
|
|
// We don't keep a transaction open for the duration of the iterator
|
|
|
// because this would expose the following semantics to consumer code:
|
|
// because this would expose the following semantics to consumer code:
|
|
@@ -650,44 +484,51 @@ func (iterator *ServerEntryIterator) Reset() error {
|
|
|
// transaction is open.
|
|
// transaction is open.
|
|
|
// (https://github.com/boltdb/bolt)
|
|
// (https://github.com/boltdb/bolt)
|
|
|
//
|
|
//
|
|
|
- // So the underlying serverEntriesBucket could change after the serverEntryIds
|
|
|
|
|
|
|
+ // So the underlying serverEntriesBucket could change after the serverEntryIDs
|
|
|
// list is built.
|
|
// list is built.
|
|
|
|
|
|
|
|
- var serverEntryIds []string
|
|
|
|
|
|
|
+ var serverEntryIDs [][]byte
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- var err error
|
|
|
|
|
- serverEntryIds, err = getRankedServerEntries(tx)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return err
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+
|
|
|
|
|
+ bucket := tx.bucket(datastoreKeyValueBucket)
|
|
|
|
|
|
|
|
- skipServerEntryIds := make(map[string]bool)
|
|
|
|
|
- for _, serverEntryId := range serverEntryIds {
|
|
|
|
|
- skipServerEntryIds[serverEntryId] = true
|
|
|
|
|
|
|
+ serverEntryIDs = make([][]byte, 0)
|
|
|
|
|
+ shuffleHead := 0
|
|
|
|
|
+
|
|
|
|
|
+ var affinityServerEntryID []byte
|
|
|
|
|
+ if iterator.applyServerAffinity {
|
|
|
|
|
+ affinityServerEntryID = bucket.get(datastoreAffinityServerEntryIDKey)
|
|
|
|
|
+ if affinityServerEntryID != nil {
|
|
|
|
|
+ serverEntryIDs = append(serverEntryIDs, append([]byte(nil), affinityServerEntryID...))
|
|
|
|
|
+ shuffleHead = 1
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(serverEntriesBucket))
|
|
|
|
|
- cursor := bucket.Cursor()
|
|
|
|
|
- for key, _ := cursor.Last(); key != nil; key, _ = cursor.Prev() {
|
|
|
|
|
- serverEntryId := string(key)
|
|
|
|
|
- if _, ok := skipServerEntryIds[serverEntryId]; ok {
|
|
|
|
|
- continue
|
|
|
|
|
|
|
+ bucket = tx.bucket(datastoreServerEntriesBucket)
|
|
|
|
|
+ cursor := bucket.cursor()
|
|
|
|
|
+ for key := cursor.firstKey(); key != nil; key = cursor.nextKey() {
|
|
|
|
|
+ if affinityServerEntryID != nil {
|
|
|
|
|
+ if bytes.Equal(affinityServerEntryID, key) {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- serverEntryIds = append(serverEntryIds, serverEntryId)
|
|
|
|
|
|
|
+ serverEntryIDs = append(serverEntryIDs, append([]byte(nil), key...))
|
|
|
|
|
+ }
|
|
|
|
|
+ cursor.close()
|
|
|
|
|
+
|
|
|
|
|
+ for i := len(serverEntryIDs) - 1; i > shuffleHead-1; i-- {
|
|
|
|
|
+ j := rand.Intn(i+1-shuffleHead) + shuffleHead
|
|
|
|
|
+ serverEntryIDs[i], serverEntryIDs[j] = serverEntryIDs[j], serverEntryIDs[i]
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return common.ContextError(err)
|
|
return common.ContextError(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for i := len(serverEntryIds) - 1; i > iterator.shuffleHeadLength-1; i-- {
|
|
|
|
|
- j := rand.Intn(i+1-iterator.shuffleHeadLength) + iterator.shuffleHeadLength
|
|
|
|
|
- serverEntryIds[i], serverEntryIds[j] = serverEntryIds[j], serverEntryIds[i]
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- iterator.serverEntryIds = serverEntryIds
|
|
|
|
|
|
|
+ iterator.serverEntryIDs = serverEntryIDs
|
|
|
iterator.serverEntryIndex = 0
|
|
iterator.serverEntryIndex = 0
|
|
|
|
|
|
|
|
return nil
|
|
return nil
|
|
@@ -695,7 +536,7 @@ func (iterator *ServerEntryIterator) Reset() error {
|
|
|
|
|
|
|
|
// Close cleans up resources associated with a ServerEntryIterator.
|
|
// Close cleans up resources associated with a ServerEntryIterator.
|
|
|
func (iterator *ServerEntryIterator) Close() {
|
|
func (iterator *ServerEntryIterator) Close() {
|
|
|
- iterator.serverEntryIds = nil
|
|
|
|
|
|
|
+ iterator.serverEntryIDs = nil
|
|
|
iterator.serverEntryIndex = 0
|
|
iterator.serverEntryIndex = 0
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -724,19 +565,19 @@ func (iterator *ServerEntryIterator) Next() (*protocol.ServerEntry, error) {
|
|
|
// Loop until we have the next server entry that matches the iterator
|
|
// Loop until we have the next server entry that matches the iterator
|
|
|
// filter requirements.
|
|
// filter requirements.
|
|
|
for {
|
|
for {
|
|
|
- if iterator.serverEntryIndex >= len(iterator.serverEntryIds) {
|
|
|
|
|
|
|
+ if iterator.serverEntryIndex >= len(iterator.serverEntryIDs) {
|
|
|
// There is no next item
|
|
// There is no next item
|
|
|
return nil, nil
|
|
return nil, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- serverEntryId := iterator.serverEntryIds[iterator.serverEntryIndex]
|
|
|
|
|
|
|
+ serverEntryID := iterator.serverEntryIDs[iterator.serverEntryIndex]
|
|
|
iterator.serverEntryIndex += 1
|
|
iterator.serverEntryIndex += 1
|
|
|
|
|
|
|
|
var data []byte
|
|
var data []byte
|
|
|
|
|
|
|
|
- err = dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(serverEntriesBucket))
|
|
|
|
|
- value := bucket.Get([]byte(serverEntryId))
|
|
|
|
|
|
|
+ err = datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreServerEntriesBucket)
|
|
|
|
|
+ value := bucket.get(serverEntryID)
|
|
|
if value != nil {
|
|
if value != nil {
|
|
|
// Must make a copy as slice is only valid within transaction.
|
|
// Must make a copy as slice is only valid within transaction.
|
|
|
data = make([]byte, len(value))
|
|
data = make([]byte, len(value))
|
|
@@ -751,7 +592,7 @@ func (iterator *ServerEntryIterator) Next() (*protocol.ServerEntry, error) {
|
|
|
if data == nil {
|
|
if data == nil {
|
|
|
// In case of data corruption or a bug causing this condition,
|
|
// In case of data corruption or a bug causing this condition,
|
|
|
// do not stop iterating.
|
|
// do not stop iterating.
|
|
|
- NoticeAlert("ServerEntryIterator.Next: unexpected missing server entry: %s", serverEntryId)
|
|
|
|
|
|
|
+ NoticeAlert("ServerEntryIterator.Next: unexpected missing server entry: %s", string(serverEntryID))
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -763,6 +604,10 @@ func (iterator *ServerEntryIterator) Next() (*protocol.ServerEntry, error) {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if iterator.serverEntryIndex%datastoreServerEntryFetchGCThreshold == 0 {
|
|
|
|
|
+ defaultGarbageCollection()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Check filter requirements
|
|
// Check filter requirements
|
|
|
|
|
|
|
|
if iterator.isTacticsServerEntryIterator {
|
|
if iterator.isTacticsServerEntryIterator {
|
|
@@ -798,13 +643,13 @@ func MakeCompatibleServerEntry(serverEntry *protocol.ServerEntry) *protocol.Serv
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func scanServerEntries(scanner func(*protocol.ServerEntry)) error {
|
|
func scanServerEntries(scanner func(*protocol.ServerEntry)) error {
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(serverEntriesBucket))
|
|
|
|
|
- cursor := bucket.Cursor()
|
|
|
|
|
-
|
|
|
|
|
- for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
|
|
|
|
|
- serverEntry := new(protocol.ServerEntry)
|
|
|
|
|
- err := json.Unmarshal(value, serverEntry)
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreServerEntriesBucket)
|
|
|
|
|
+ cursor := bucket.cursor()
|
|
|
|
|
+ n := 0
|
|
|
|
|
+ for key, value := cursor.first(); key != nil; key, value = cursor.next() {
|
|
|
|
|
+ var serverEntry *protocol.ServerEntry
|
|
|
|
|
+ err := json.Unmarshal(value, &serverEntry)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
// In case of data corruption or a bug causing this condition,
|
|
// In case of data corruption or a bug causing this condition,
|
|
|
// do not stop iterating.
|
|
// do not stop iterating.
|
|
@@ -812,8 +657,14 @@ func scanServerEntries(scanner func(*protocol.ServerEntry)) error {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
scanner(serverEntry)
|
|
scanner(serverEntry)
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
|
|
+ n += 1
|
|
|
|
|
+ if n == datastoreServerEntryFetchGCThreshold {
|
|
|
|
|
+ defaultGarbageCollection()
|
|
|
|
|
+ n = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ cursor.close()
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -906,33 +757,17 @@ func ReportAvailableRegions(config *Config, limitState *limitTunnelProtocolsStat
|
|
|
NoticeAvailableEgressRegions(regionList)
|
|
NoticeAvailableEgressRegions(regionList)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// GetServerEntryIpAddresses returns an array containing
|
|
|
|
|
-// all stored server IP addresses.
|
|
|
|
|
-func GetServerEntryIpAddresses() ([]string, error) {
|
|
|
|
|
-
|
|
|
|
|
- ipAddresses := make([]string, 0)
|
|
|
|
|
- err := scanServerEntries(func(serverEntry *protocol.ServerEntry) {
|
|
|
|
|
- ipAddresses = append(ipAddresses, serverEntry.IpAddress)
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, common.ContextError(err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return ipAddresses, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// SetSplitTunnelRoutes updates the cached routes data for
|
|
// SetSplitTunnelRoutes updates the cached routes data for
|
|
|
// the given region. The associated etag is also stored and
|
|
// the given region. The associated etag is also stored and
|
|
|
// used to make efficient web requests for updates to the data.
|
|
// used to make efficient web requests for updates to the data.
|
|
|
func SetSplitTunnelRoutes(region, etag string, data []byte) error {
|
|
func SetSplitTunnelRoutes(region, etag string, data []byte) error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(splitTunnelRouteETagsBucket))
|
|
|
|
|
- err := bucket.Put([]byte(region), []byte(etag))
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreSplitTunnelRouteETagsBucket)
|
|
|
|
|
+ err := bucket.put([]byte(region), []byte(etag))
|
|
|
|
|
|
|
|
- bucket = tx.Bucket([]byte(splitTunnelRouteDataBucket))
|
|
|
|
|
- err = bucket.Put([]byte(region), data)
|
|
|
|
|
|
|
+ bucket = tx.bucket(datastoreSplitTunnelRouteDataBucket)
|
|
|
|
|
+ err = bucket.put([]byte(region), data)
|
|
|
return err
|
|
return err
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -948,9 +783,9 @@ func GetSplitTunnelRoutesETag(region string) (string, error) {
|
|
|
|
|
|
|
|
var etag string
|
|
var etag string
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(splitTunnelRouteETagsBucket))
|
|
|
|
|
- etag = string(bucket.Get([]byte(region)))
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreSplitTunnelRouteETagsBucket)
|
|
|
|
|
+ etag = string(bucket.get([]byte(region)))
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -966,9 +801,9 @@ func GetSplitTunnelRoutesData(region string) ([]byte, error) {
|
|
|
|
|
|
|
|
var data []byte
|
|
var data []byte
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(splitTunnelRouteDataBucket))
|
|
|
|
|
- value := bucket.Get([]byte(region))
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreSplitTunnelRouteDataBucket)
|
|
|
|
|
+ value := bucket.get([]byte(region))
|
|
|
if value != nil {
|
|
if value != nil {
|
|
|
// Must make a copy as slice is only valid within transaction.
|
|
// Must make a copy as slice is only valid within transaction.
|
|
|
data = make([]byte, len(value))
|
|
data = make([]byte, len(value))
|
|
@@ -988,9 +823,9 @@ func GetSplitTunnelRoutesData(region string) ([]byte, error) {
|
|
|
// encoded or decoded or otherwise canonicalized.
|
|
// encoded or decoded or otherwise canonicalized.
|
|
|
func SetUrlETag(url, etag string) error {
|
|
func SetUrlETag(url, etag string) error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(urlETagsBucket))
|
|
|
|
|
- err := bucket.Put([]byte(url), []byte(etag))
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreUrlETagsBucket)
|
|
|
|
|
+ err := bucket.put([]byte(url), []byte(etag))
|
|
|
return err
|
|
return err
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1006,9 +841,9 @@ func GetUrlETag(url string) (string, error) {
|
|
|
|
|
|
|
|
var etag string
|
|
var etag string
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(urlETagsBucket))
|
|
|
|
|
- etag = string(bucket.Get([]byte(url)))
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreUrlETagsBucket)
|
|
|
|
|
+ etag = string(bucket.get([]byte(url)))
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1021,9 +856,9 @@ func GetUrlETag(url string) (string, error) {
|
|
|
// SetKeyValue stores a key/value pair.
|
|
// SetKeyValue stores a key/value pair.
|
|
|
func SetKeyValue(key, value string) error {
|
|
func SetKeyValue(key, value string) error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(keyValueBucket))
|
|
|
|
|
- err := bucket.Put([]byte(key), []byte(value))
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreKeyValueBucket)
|
|
|
|
|
+ err := bucket.put([]byte(key), []byte(value))
|
|
|
return err
|
|
return err
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1039,9 +874,9 @@ func GetKeyValue(key string) (string, error) {
|
|
|
|
|
|
|
|
var value string
|
|
var value string
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(keyValueBucket))
|
|
|
|
|
- value = string(bucket.Get([]byte(key)))
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreKeyValueBucket)
|
|
|
|
|
+ value = string(bucket.get([]byte(key)))
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1065,7 +900,7 @@ var persistentStatStateUnreported = []byte("0")
|
|
|
var persistentStatStateReporting = []byte("1")
|
|
var persistentStatStateReporting = []byte("1")
|
|
|
|
|
|
|
|
var persistentStatTypes = []string{
|
|
var persistentStatTypes = []string{
|
|
|
- PERSISTENT_STAT_TYPE_REMOTE_SERVER_LIST,
|
|
|
|
|
|
|
+ datastorePersistentStatTypeRemoteServerList,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// StorePersistentStat adds a new persistent stat record, which
|
|
// StorePersistentStat adds a new persistent stat record, which
|
|
@@ -1084,9 +919,9 @@ func StorePersistentStat(statType string, stat []byte) error {
|
|
|
return common.ContextError(fmt.Errorf("invalid persistent stat type: %s", statType))
|
|
return common.ContextError(fmt.Errorf("invalid persistent stat type: %s", statType))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(statType))
|
|
|
|
|
- err := bucket.Put(stat, persistentStatStateUnreported)
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket([]byte(statType))
|
|
|
|
|
+ err := bucket.put(stat, persistentStatStateUnreported)
|
|
|
return err
|
|
return err
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1103,18 +938,19 @@ func CountUnreportedPersistentStats() int {
|
|
|
|
|
|
|
|
unreported := 0
|
|
unreported := 0
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
for _, statType := range persistentStatTypes {
|
|
for _, statType := range persistentStatTypes {
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(statType))
|
|
|
|
|
- cursor := bucket.Cursor()
|
|
|
|
|
- for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
|
|
|
|
|
|
|
+ bucket := tx.bucket([]byte(statType))
|
|
|
|
|
+ cursor := bucket.cursor()
|
|
|
|
|
+ for key, value := cursor.first(); key != nil; key, value = cursor.next() {
|
|
|
if 0 == bytes.Compare(value, persistentStatStateUnreported) {
|
|
if 0 == bytes.Compare(value, persistentStatStateUnreported) {
|
|
|
unreported++
|
|
unreported++
|
|
|
break
|
|
break
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ cursor.close()
|
|
|
}
|
|
}
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
@@ -1136,15 +972,15 @@ func TakeOutUnreportedPersistentStats(maxCount int) (map[string][][]byte, error)
|
|
|
|
|
|
|
|
stats := make(map[string][][]byte)
|
|
stats := make(map[string][][]byte)
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
count := 0
|
|
count := 0
|
|
|
|
|
|
|
|
for _, statType := range persistentStatTypes {
|
|
for _, statType := range persistentStatTypes {
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(statType))
|
|
|
|
|
- cursor := bucket.Cursor()
|
|
|
|
|
- for key, value := cursor.First(); key != nil; key, value = cursor.Next() {
|
|
|
|
|
|
|
+ bucket := tx.bucket([]byte(statType))
|
|
|
|
|
+ cursor := bucket.cursor()
|
|
|
|
|
+ for key, value := cursor.first(); key != nil; key, value = cursor.next() {
|
|
|
|
|
|
|
|
if count >= maxCount {
|
|
if count >= maxCount {
|
|
|
break
|
|
break
|
|
@@ -1174,9 +1010,10 @@ func TakeOutUnreportedPersistentStats(maxCount int) (map[string][][]byte, error)
|
|
|
count += 1
|
|
count += 1
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ cursor.close()
|
|
|
|
|
|
|
|
for _, key := range stats[statType] {
|
|
for _, key := range stats[statType] {
|
|
|
- err := bucket.Put(key, persistentStatStateReporting)
|
|
|
|
|
|
|
+ err := bucket.put(key, persistentStatStateReporting)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -1197,13 +1034,13 @@ func TakeOutUnreportedPersistentStats(maxCount int) (map[string][][]byte, error)
|
|
|
// stat records to StateUnreported.
|
|
// stat records to StateUnreported.
|
|
|
func PutBackUnreportedPersistentStats(stats map[string][][]byte) error {
|
|
func PutBackUnreportedPersistentStats(stats map[string][][]byte) error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
for _, statType := range persistentStatTypes {
|
|
for _, statType := range persistentStatTypes {
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(statType))
|
|
|
|
|
|
|
+ bucket := tx.bucket([]byte(statType))
|
|
|
for _, key := range stats[statType] {
|
|
for _, key := range stats[statType] {
|
|
|
- err := bucket.Put(key, persistentStatStateUnreported)
|
|
|
|
|
|
|
+ err := bucket.put(key, persistentStatStateUnreported)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -1224,13 +1061,13 @@ func PutBackUnreportedPersistentStats(stats map[string][][]byte) error {
|
|
|
// stat records that were successfully reported.
|
|
// stat records that were successfully reported.
|
|
|
func ClearReportedPersistentStats(stats map[string][][]byte) error {
|
|
func ClearReportedPersistentStats(stats map[string][][]byte) error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
for _, statType := range persistentStatTypes {
|
|
for _, statType := range persistentStatTypes {
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(statType))
|
|
|
|
|
|
|
+ bucket := tx.bucket([]byte(statType))
|
|
|
for _, key := range stats[statType] {
|
|
for _, key := range stats[statType] {
|
|
|
- err := bucket.Delete(key)
|
|
|
|
|
|
|
+ err := bucket.delete(key)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -1253,22 +1090,23 @@ func ClearReportedPersistentStats(stats map[string][][]byte) error {
|
|
|
// persistent records in StateReporting were reported or not.
|
|
// persistent records in StateReporting were reported or not.
|
|
|
func resetAllPersistentStatsToUnreported() error {
|
|
func resetAllPersistentStatsToUnreported() error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
|
|
|
for _, statType := range persistentStatTypes {
|
|
for _, statType := range persistentStatTypes {
|
|
|
|
|
|
|
|
- bucket := tx.Bucket([]byte(statType))
|
|
|
|
|
|
|
+ bucket := tx.bucket([]byte(statType))
|
|
|
resetKeys := make([][]byte, 0)
|
|
resetKeys := make([][]byte, 0)
|
|
|
- cursor := bucket.Cursor()
|
|
|
|
|
- for key, _ := cursor.First(); key != nil; key, _ = cursor.Next() {
|
|
|
|
|
|
|
+ cursor := bucket.cursor()
|
|
|
|
|
+ for key := cursor.firstKey(); key != nil; key = cursor.nextKey() {
|
|
|
resetKeys = append(resetKeys, key)
|
|
resetKeys = append(resetKeys, key)
|
|
|
}
|
|
}
|
|
|
|
|
+ cursor.close()
|
|
|
// TODO: data mutation is done outside cursor. Is this
|
|
// TODO: data mutation is done outside cursor. Is this
|
|
|
// strictly necessary in this case? As is, this means
|
|
// strictly necessary in this case? As is, this means
|
|
|
// all stats need to be loaded into memory at once.
|
|
// all stats need to be loaded into memory at once.
|
|
|
// https://godoc.org/github.com/boltdb/bolt#Cursor
|
|
// https://godoc.org/github.com/boltdb/bolt#Cursor
|
|
|
for _, key := range resetKeys {
|
|
for _, key := range resetKeys {
|
|
|
- err := bucket.Put(key, persistentStatStateUnreported)
|
|
|
|
|
|
|
+ err := bucket.put(key, persistentStatStateUnreported)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -1290,12 +1128,13 @@ func CountSLOKs() int {
|
|
|
|
|
|
|
|
count := 0
|
|
count := 0
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(slokBucket))
|
|
|
|
|
- cursor := bucket.Cursor()
|
|
|
|
|
- for key, _ := cursor.First(); key != nil; key, _ = cursor.Next() {
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreSLOKsBucket)
|
|
|
|
|
+ cursor := bucket.cursor()
|
|
|
|
|
+ for key := cursor.firstKey(); key != nil; key = cursor.nextKey() {
|
|
|
count++
|
|
count++
|
|
|
}
|
|
}
|
|
|
|
|
+ cursor.close()
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1310,12 +1149,8 @@ func CountSLOKs() int {
|
|
|
// DeleteSLOKs deletes all SLOK records.
|
|
// DeleteSLOKs deletes all SLOK records.
|
|
|
func DeleteSLOKs() error {
|
|
func DeleteSLOKs() error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(slokBucket))
|
|
|
|
|
- return bucket.ForEach(
|
|
|
|
|
- func(id, _ []byte) error {
|
|
|
|
|
- return bucket.Delete(id)
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ return tx.clearBucket(datastoreSLOKsBucket)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -1331,10 +1166,10 @@ func SetSLOK(id, key []byte) (bool, error) {
|
|
|
|
|
|
|
|
var duplicate bool
|
|
var duplicate bool
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(slokBucket))
|
|
|
|
|
- duplicate = bucket.Get(id) != nil
|
|
|
|
|
- err := bucket.Put([]byte(id), []byte(key))
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreSLOKsBucket)
|
|
|
|
|
+ duplicate = bucket.get(id) != nil
|
|
|
|
|
+ err := bucket.put([]byte(id), []byte(key))
|
|
|
return err
|
|
return err
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1351,9 +1186,9 @@ func GetSLOK(id []byte) ([]byte, error) {
|
|
|
|
|
|
|
|
var key []byte
|
|
var key []byte
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket([]byte(slokBucket))
|
|
|
|
|
- key = bucket.Get(id)
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(datastoreSLOKsBucket)
|
|
|
|
|
+ key = bucket.get(id)
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1369,19 +1204,19 @@ type TacticsStorer struct {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (t *TacticsStorer) SetTacticsRecord(networkID string, record []byte) error {
|
|
func (t *TacticsStorer) SetTacticsRecord(networkID string, record []byte) error {
|
|
|
- return setBucketValue([]byte(tacticsBucket), []byte(networkID), record)
|
|
|
|
|
|
|
+ return setBucketValue(datastoreTacticsBucket, []byte(networkID), record)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (t *TacticsStorer) GetTacticsRecord(networkID string) ([]byte, error) {
|
|
func (t *TacticsStorer) GetTacticsRecord(networkID string) ([]byte, error) {
|
|
|
- return getBucketValue([]byte(tacticsBucket), []byte(networkID))
|
|
|
|
|
|
|
+ return getBucketValue(datastoreTacticsBucket, []byte(networkID))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (t *TacticsStorer) SetSpeedTestSamplesRecord(networkID string, record []byte) error {
|
|
func (t *TacticsStorer) SetSpeedTestSamplesRecord(networkID string, record []byte) error {
|
|
|
- return setBucketValue([]byte(speedTestSamplesBucket), []byte(networkID), record)
|
|
|
|
|
|
|
+ return setBucketValue(datastoreSpeedTestSamplesBucket, []byte(networkID), record)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (t *TacticsStorer) GetSpeedTestSamplesRecord(networkID string) ([]byte, error) {
|
|
func (t *TacticsStorer) GetSpeedTestSamplesRecord(networkID string) ([]byte, error) {
|
|
|
- return getBucketValue([]byte(speedTestSamplesBucket), []byte(networkID))
|
|
|
|
|
|
|
+ return getBucketValue(datastoreSpeedTestSamplesBucket, []byte(networkID))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// GetTacticsStorer creates a TacticsStorer.
|
|
// GetTacticsStorer creates a TacticsStorer.
|
|
@@ -1391,9 +1226,9 @@ func GetTacticsStorer() *TacticsStorer {
|
|
|
|
|
|
|
|
func setBucketValue(bucket, key, value []byte) error {
|
|
func setBucketValue(bucket, key, value []byte) error {
|
|
|
|
|
|
|
|
- err := dataStoreUpdate(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket(bucket)
|
|
|
|
|
- err := bucket.Put(key, value)
|
|
|
|
|
|
|
+ err := datastoreUpdate(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(bucket)
|
|
|
|
|
+ err := bucket.put(key, value)
|
|
|
return err
|
|
return err
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -1408,9 +1243,9 @@ func getBucketValue(bucket, key []byte) ([]byte, error) {
|
|
|
|
|
|
|
|
var value []byte
|
|
var value []byte
|
|
|
|
|
|
|
|
- err := dataStoreView(func(tx *bolt.Tx) error {
|
|
|
|
|
- bucket := tx.Bucket(bucket)
|
|
|
|
|
- value = bucket.Get(key)
|
|
|
|
|
|
|
+ err := datastoreView(func(tx *datastoreTx) error {
|
|
|
|
|
+ bucket := tx.bucket(bucket)
|
|
|
|
|
+ value = bucket.get(key)
|
|
|
return nil
|
|
return nil
|
|
|
})
|
|
})
|
|
|
|
|
|