github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/network/simulation/simulation.go (about) 1 // Copyleft 2018 The susy-graviton Authors 2 // This file is part of the susy-graviton library. 3 // 4 // The susy-graviton library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The susy-graviton library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the susy-graviton library. If not, see <http://www.gnu.org/licenses/>. 16 17 package simulation 18 19 import ( 20 "context" 21 "errors" 22 "net/http" 23 "sync" 24 "time" 25 26 "github.com/susy-go/susy-graviton/log" 27 "github.com/susy-go/susy-graviton/node" 28 "github.com/susy-go/susy-graviton/p2p/enode" 29 "github.com/susy-go/susy-graviton/p2p/simulations" 30 "github.com/susy-go/susy-graviton/p2p/simulations/adapters" 31 "github.com/susy-go/susy-graviton/swarm/network" 32 ) 33 34 // Common errors that are returned by functions in this package. 35 var ( 36 ErrNodeNotFound = errors.New("node not found") 37 ) 38 39 // Simulation provides methods on network, nodes and services 40 // to manage them. 41 type Simulation struct { 42 // Net is exposed as a way to access lower level functionalities 43 // of p2p/simulations.Network. 44 Net *simulations.Network 45 46 serviceNames []string 47 cleanupFuncs []func() 48 buckets map[enode.ID]*sync.Map 49 shutdownWG sync.WaitGroup 50 done chan struct{} 51 mu sync.RWMutex 52 neighbourhoodSize int 53 54 httpSrv *http.Server //attach a HTTP server via SimulationOptions 55 handler *simulations.Server //HTTP handler for the server 56 runC chan struct{} //channel where frontend signals it is ready 57 } 58 59 // ServiceFunc is used in New to declare new service constructor. 60 // The first argument provides ServiceContext from the adapters package 61 // giving for example the access to NodeID. Second argument is the sync.Map 62 // where all "global" state related to the service should be kept. 63 // All cleanups needed for constructed service and any other constructed 64 // objects should ne provided in a single returned cleanup function. 65 // Returned cleanup function will be called by Close function 66 // after network shutdown. 67 type ServiceFunc func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) 68 69 // New creates a new simulation instance 70 // Services map must have unique keys as service names and 71 // every ServiceFunc must return a node.Service of the unique type. 72 // This restriction is required by node.Node.Start() function 73 // which is used to start node.Service returned by ServiceFunc. 74 func New(services map[string]ServiceFunc) (s *Simulation) { 75 s = &Simulation{ 76 buckets: make(map[enode.ID]*sync.Map), 77 done: make(chan struct{}), 78 neighbourhoodSize: network.NewKadParams().NeighbourhoodSize, 79 } 80 81 adapterServices := make(map[string]adapters.ServiceFunc, len(services)) 82 for name, serviceFunc := range services { 83 // Scope this variables correctly 84 // as they will be in the adapterServices[name] function accessed later. 85 name, serviceFunc := name, serviceFunc 86 s.serviceNames = append(s.serviceNames, name) 87 adapterServices[name] = func(ctx *adapters.ServiceContext) (node.Service, error) { 88 b := new(sync.Map) 89 service, cleanup, err := serviceFunc(ctx, b) 90 if err != nil { 91 return nil, err 92 } 93 s.mu.Lock() 94 defer s.mu.Unlock() 95 if cleanup != nil { 96 s.cleanupFuncs = append(s.cleanupFuncs, cleanup) 97 } 98 s.buckets[ctx.Config.ID] = b 99 return service, nil 100 } 101 } 102 103 s.Net = simulations.NewNetwork( 104 adapters.NewTCPAdapter(adapterServices), 105 &simulations.NetworkConfig{ID: "0"}, 106 ) 107 108 return s 109 } 110 111 // RunFunc is the function that will be called 112 // on Simulation.Run method call. 113 type RunFunc func(context.Context, *Simulation) error 114 115 // Result is the returned value of Simulation.Run method. 116 type Result struct { 117 Duration time.Duration 118 Error error 119 } 120 121 // Run calls the RunFunc function while taking care of 122 // cancellation provided through the Context. 123 func (s *Simulation) Run(ctx context.Context, f RunFunc) (r Result) { 124 //if the option is set to run a HTTP server with the simulation, 125 //init the server and start it 126 start := time.Now() 127 if s.httpSrv != nil { 128 log.Info("Waiting for frontend to be ready...(send POST /runsim to HTTP server)") 129 //wait for the frontend to connect 130 select { 131 case <-s.runC: 132 case <-ctx.Done(): 133 return Result{ 134 Duration: time.Since(start), 135 Error: ctx.Err(), 136 } 137 } 138 log.Info("Received signal from frontend - starting simulation run.") 139 } 140 errc := make(chan error) 141 quit := make(chan struct{}) 142 defer close(quit) 143 go func() { 144 select { 145 case errc <- f(ctx, s): 146 case <-quit: 147 } 148 }() 149 var err error 150 select { 151 case <-ctx.Done(): 152 err = ctx.Err() 153 case err = <-errc: 154 } 155 return Result{ 156 Duration: time.Since(start), 157 Error: err, 158 } 159 } 160 161 // Maximal number of parallel calls to cleanup functions on 162 // Simulation.Close. 163 var maxParallelCleanups = 10 164 165 // Close calls all cleanup functions that are returned by 166 // ServiceFunc, waits for all of them to finish and other 167 // functions that explicitly block shutdownWG 168 // (like Simulation.PeerEvents) and shuts down the network 169 // at the end. It is used to clean all resources from the 170 // simulation. 171 func (s *Simulation) Close() { 172 close(s.done) 173 174 sem := make(chan struct{}, maxParallelCleanups) 175 s.mu.RLock() 176 cleanupFuncs := make([]func(), len(s.cleanupFuncs)) 177 for i, f := range s.cleanupFuncs { 178 if f != nil { 179 cleanupFuncs[i] = f 180 } 181 } 182 s.mu.RUnlock() 183 var cleanupWG sync.WaitGroup 184 for _, cleanup := range cleanupFuncs { 185 cleanupWG.Add(1) 186 sem <- struct{}{} 187 go func(cleanup func()) { 188 defer cleanupWG.Done() 189 defer func() { <-sem }() 190 191 cleanup() 192 }(cleanup) 193 } 194 cleanupWG.Wait() 195 196 if s.httpSrv != nil { 197 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 198 defer cancel() 199 err := s.httpSrv.Shutdown(ctx) 200 if err != nil { 201 log.Error("Error shutting down HTTP server!", "err", err) 202 } 203 close(s.runC) 204 } 205 206 s.shutdownWG.Wait() 207 s.Net.Shutdown() 208 } 209 210 // Done returns a channel that is closed when the simulation 211 // is closed by Close method. It is useful for signaling termination 212 // of all possible goroutines that are created within the test. 213 func (s *Simulation) Done() <-chan struct{} { 214 return s.done 215 }