github.com/divan/go-ethereum@v1.8.14-0.20180820134928-1de9ada4016d/swarm/network/simulation/simulation.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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 go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY 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 go-ethereum 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/ethereum/go-ethereum/log" 27 "github.com/ethereum/go-ethereum/node" 28 "github.com/ethereum/go-ethereum/p2p/discover" 29 "github.com/ethereum/go-ethereum/p2p/simulations" 30 "github.com/ethereum/go-ethereum/p2p/simulations/adapters" 31 ) 32 33 // Common errors that are returned by functions in this package. 34 var ( 35 ErrNodeNotFound = errors.New("node not found") 36 ErrNoPivotNode = errors.New("no pivot node set") 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[discover.NodeID]*sync.Map 49 pivotNodeID *discover.NodeID 50 shutdownWG sync.WaitGroup 51 done chan struct{} 52 mu sync.RWMutex 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 with new 70 // simulations.Network initialized with provided services. 71 func New(services map[string]ServiceFunc) (s *Simulation) { 72 s = &Simulation{ 73 buckets: make(map[discover.NodeID]*sync.Map), 74 done: make(chan struct{}), 75 } 76 77 adapterServices := make(map[string]adapters.ServiceFunc, len(services)) 78 for name, serviceFunc := range services { 79 s.serviceNames = append(s.serviceNames, name) 80 adapterServices[name] = func(ctx *adapters.ServiceContext) (node.Service, error) { 81 b := new(sync.Map) 82 service, cleanup, err := serviceFunc(ctx, b) 83 if err != nil { 84 return nil, err 85 } 86 s.mu.Lock() 87 defer s.mu.Unlock() 88 if cleanup != nil { 89 s.cleanupFuncs = append(s.cleanupFuncs, cleanup) 90 } 91 s.buckets[ctx.Config.ID] = b 92 return service, nil 93 } 94 } 95 96 s.Net = simulations.NewNetwork( 97 adapters.NewSimAdapter(adapterServices), 98 &simulations.NetworkConfig{ID: "0"}, 99 ) 100 101 return s 102 } 103 104 // RunFunc is the function that will be called 105 // on Simulation.Run method call. 106 type RunFunc func(context.Context, *Simulation) error 107 108 // Result is the returned value of Simulation.Run method. 109 type Result struct { 110 Duration time.Duration 111 Error error 112 } 113 114 // Run calls the RunFunc function while taking care of 115 // cancelation provided through the Context. 116 func (s *Simulation) Run(ctx context.Context, f RunFunc) (r Result) { 117 //if the option is set to run a HTTP server with the simulation, 118 //init the server and start it 119 start := time.Now() 120 if s.httpSrv != nil { 121 log.Info("Waiting for frontend to be ready...(send POST /runsim to HTTP server)") 122 //wait for the frontend to connect 123 select { 124 case <-s.runC: 125 case <-ctx.Done(): 126 return Result{ 127 Duration: time.Since(start), 128 Error: ctx.Err(), 129 } 130 } 131 log.Info("Received signal from frontend - starting simulation run.") 132 } 133 errc := make(chan error) 134 quit := make(chan struct{}) 135 defer close(quit) 136 go func() { 137 select { 138 case errc <- f(ctx, s): 139 case <-quit: 140 } 141 }() 142 var err error 143 select { 144 case <-ctx.Done(): 145 err = ctx.Err() 146 case err = <-errc: 147 } 148 return Result{ 149 Duration: time.Since(start), 150 Error: err, 151 } 152 } 153 154 // Maximal number of parallel calls to cleanup functions on 155 // Simulation.Close. 156 var maxParallelCleanups = 10 157 158 // Close calls all cleanup functions that are returned by 159 // ServiceFunc, waits for all of them to finish and other 160 // functions that explicitly block shutdownWG 161 // (like Simulation.PeerEvents) and shuts down the network 162 // at the end. It is used to clean all resources from the 163 // simulation. 164 func (s *Simulation) Close() { 165 close(s.done) 166 167 // Close all connections before calling the Network Shutdown. 168 // It is possible that p2p.Server.Stop will block if there are 169 // existing connections. 170 for _, c := range s.Net.Conns { 171 if c.Up { 172 s.Net.Disconnect(c.One, c.Other) 173 } 174 } 175 s.shutdownWG.Wait() 176 s.Net.Shutdown() 177 178 sem := make(chan struct{}, maxParallelCleanups) 179 s.mu.RLock() 180 cleanupFuncs := make([]func(), len(s.cleanupFuncs)) 181 for i, f := range s.cleanupFuncs { 182 if f != nil { 183 cleanupFuncs[i] = f 184 } 185 } 186 s.mu.RUnlock() 187 var cleanupWG sync.WaitGroup 188 for _, cleanup := range cleanupFuncs { 189 cleanupWG.Add(1) 190 sem <- struct{}{} 191 go func(cleanup func()) { 192 defer cleanupWG.Done() 193 defer func() { <-sem }() 194 195 cleanup() 196 }(cleanup) 197 } 198 cleanupWG.Wait() 199 200 if s.httpSrv != nil { 201 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 202 defer cancel() 203 err := s.httpSrv.Shutdown(ctx) 204 if err != nil { 205 log.Error("Error shutting down HTTP server!", "err", err) 206 } 207 close(s.runC) 208 } 209 } 210 211 // Done returns a channel that is closed when the simulation 212 // is closed by Close method. It is useful for signaling termination 213 // of all possible goroutines that are created within the test. 214 func (s *Simulation) Done() <-chan struct{} { 215 return s.done 216 }