Files
core/dht/Information Request.go
Kleissner 891e621e8a DHT: Add Search Client which runs the search in concurrent levels. This means faster results.
Add function FilterSearchStatus to get notified on the status of searches for debugging.
FindNode uses the new search client.
2021-07-28 01:28:31 +02:00

114 lines
3.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
ResultChanExt chan *NodeMessage // External result channel to use instead
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
)
const messageChannelSize = 100
// 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, messageChannelSize),
TerminateSignal: make(chan struct{}),
Action: Action,
Key: Key,
Nodes: Nodes,
ActiveNodes: uint64(len(Nodes)),
}
return
}
// CollectResults collects all information request responses within the given timeout.
func (ir *InformationRequest) CollectResults(timeout time.Duration) (results []*NodeMessage) {
for {
select {
case result, ok := <-ir.ResultChan:
if !ok { // channel closed?
return
}
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)
}
// Done is called when a remote node is done.
func (ir *InformationRequest) Done() {
if atomic.AddUint64(&ir.ActiveNodes, ^uint64(0)) <= 0 {
// If the counter reaches 0, it means no nodes are handling this request anymore -> terminate it.
ir.Terminate()
}
}
// QueueResult accepts incoming results and queues them to the result channel. Non-blocking.
func (ir *InformationRequest) QueueResult(message *NodeMessage) {
ir.Lock()
if !ir.IsTerminated {
targetChan := ir.ResultChan
if ir.ResultChanExt != nil {
targetChan = ir.ResultChanExt
}
select {
case targetChan <- message:
default:
}
}
ir.Unlock()
}