Files
core/dht/Information Request.go
Kleissner c44e57a0a0 Add information request list to DHT structure. It keeps track of requests and matches responses.
Break up SendRequest into individual functions SendRequestStore and SendRequestFindNode, which improves readability.
Alpha parameter must be now passed. MarkNodeAsSeen is exported.
Breaking up dht.iterate and embedding into 3 functions Get, FindNode, and FindValue.
2021-04-09 16:07:20 +02:00

137 lines
4.2 KiB
Go

/*
File Name: Information Request.go
Copyright: 2021 Peernet s.r.o.
Author: Peter Kleissner
Information requests are asynchronous queries sent to nodes.
*/
package dht
import (
"sync"
"sync/atomic"
"time"
)
// InformationRequest is an asynchronous request sent to nodes. It tracks any asynchronous replies and handles timeouts.
type InformationRequest struct {
Action int // ActionX
Key []byte // Key that is being queried
ResultChan chan *NodeMessage // Result channel
ActiveNodes uint64 // Number of nodes actively handling the request.
Nodes []*Node // Nodes that are receiving the request.
IsTerminated bool // If true, it was signaled for termination
TerminateSignal chan struct{} // gets closed on termination signal, can be used in select via "case _ = <- network.terminateSignal:"
sync.Mutex // for sychronized closing
}
// Actions for performing the information request
const (
ActionFindNode = iota // Find a node
ActionFindValue // Find a value
)
// NewInformationRequest creates a new information request and adds it to the list.
// It marks the count of nodes as active, meaning the caller should later decrease it via ActiveNodesSub.
func (dht *DHT) NewInformationRequest(Action int, Key []byte, Nodes []*Node) (ir *InformationRequest) {
ir = &InformationRequest{
ResultChan: make(chan *NodeMessage),
TerminateSignal: make(chan struct{}),
Action: Action,
Key: Key,
Nodes: Nodes,
ActiveNodes: uint64(len(Nodes)),
}
for _, node := range Nodes {
dht.irAdd(node.ID, ir)
}
return
}
// CollectResults collects all information request responses within the given timeout.
func (ir *InformationRequest) CollectResults(timeout time.Duration) (results []*NodeMessage) {
for {
select {
case result := <-ir.ResultChan:
results = append(results, result)
case <-time.After(timeout):
ir.Terminate()
return
case <-ir.TerminateSignal:
return
}
}
}
// Terminate sends the termination signal to all workers. It is safe to call Terminate multiple times.
func (ir *InformationRequest) Terminate() {
ir.Lock()
defer ir.Unlock()
if ir.IsTerminated {
return
}
// set the termination signal
ir.IsTerminated = true
close(ir.TerminateSignal) // safety guaranteed via lock
close(ir.ResultChan)
}
// ActiveNodesAdd increases the number of active nodes handling this request
func (ir *InformationRequest) ActiveNodesAdd(count uint64) {
atomic.AddUint64(&ir.ActiveNodes, count)
}
// ActiveNodesSub decreases the number of active nodes handling this request
func (ir *InformationRequest) ActiveNodesSub(count uint64) {
if atomic.AddUint64(&ir.ActiveNodes, ^uint64(count-1)) <= 0 {
// If the counter reaches 0, it means no nodes are handling this request anymore -> terminate it.
ir.Terminate()
}
}
// ---- keep track of information requests ----
// irRemove add the information request to the list
// If a request to the same node with the same key exists, it is overwritten. This will be improved. TODO: A nonce could easily fix that?
func (dht *DHT) irAdd(nodeID []byte, request *InformationRequest) {
dht.listIRmutex.Lock()
defer dht.listIRmutex.Unlock()
// The list only supports one request per target node and key. This should be improved.
lookupKey := string(nodeID) + string(request.Key)
dht.ListIR[lookupKey] = request
// auto remove from list upon termination
go func(lookupKey string, terminateChan <-chan struct{}) {
<-terminateChan
dht.irRemove(lookupKey)
}(lookupKey, request.TerminateSignal)
}
// irRemove removes the information request from the list
func (dht *DHT) irRemove(lookupKey string) {
dht.listIRmutex.Lock()
defer dht.listIRmutex.Unlock()
delete(dht.ListIR, lookupKey)
}
// IRLookup looks up the information request based on the peers public key and hash
func (dht *DHT) IRLookup(nodeID []byte, hash []byte) (request *InformationRequest) {
dht.listIRmutex.RLock()
defer dht.listIRmutex.RUnlock()
lookupKey := string(nodeID) + string(request.Key)
request = dht.ListIR[lookupKey]
return request
}