func (px *standard) sendRequest(ctx context.Context, m *dhtpb.Message, remote peer.ID) (*dhtpb.Message, error) { e := log.EventBegin(ctx, "sendRoutingRequest", px.Host.ID(), remote, logging.Pair("request", m)) defer e.Done() if err := px.Host.Connect(ctx, peer.PeerInfo{ID: remote}); err != nil { e.SetError(err) return nil, err } s, err := px.Host.NewStream(ProtocolSNR, remote) if err != nil { e.SetError(err) return nil, err } defer s.Close() r := ggio.NewDelimitedReader(s, inet.MessageSizeMax) w := ggio.NewDelimitedWriter(s) if err = w.WriteMsg(m); err != nil { e.SetError(err) return nil, err } response := &dhtpb.Message{} if err = r.ReadMsg(response); err != nil { e.SetError(err) return nil, err } // need ctx expiration? if response == nil { err := errors.New("no response to request") e.SetError(err) return nil, err } e.Append(logging.Pair("response", response)) e.Append(logging.Pair("uuid", logging.Uuid("foo"))) return response, nil }
// GetBlock attempts to retrieve a particular block from peers within the // deadline enforced by the context. func (bs *Bitswap) GetBlock(parent context.Context, k key.Key) (*blocks.Block, error) { // Any async work initiated by this function must end when this function // returns. To ensure this, derive a new context. Note that it is okay to // listen on parent in this scope, but NOT okay to pass |parent| to // functions called by this one. Otherwise those functions won't return // when this context's cancel func is executed. This is difficult to // enforce. May this comment keep you safe. ctx, cancelFunc := context.WithCancel(parent) ctx = logging.ContextWithLoggable(ctx, logging.Uuid("GetBlockRequest")) log.Event(ctx, "Bitswap.GetBlockRequest.Start", &k) defer log.Event(ctx, "Bitswap.GetBlockRequest.End", &k) defer func() { cancelFunc() }() promise, err := bs.GetBlocks(ctx, []key.Key{k}) if err != nil { return nil, err } select { case block, ok := <-promise: if !ok { select { case <-ctx.Done(): return nil, ctx.Err() default: return nil, errors.New("promise channel was closed") } } return block, nil case <-parent.Done(): return nil, parent.Err() } }
func (c *Client) FindProvidersAsync(ctx context.Context, k key.Key, max int) <-chan peer.PeerInfo { ctx = logging.ContextWithLoggable(ctx, logging.Uuid("findProviders")) defer log.EventBegin(ctx, "findProviders", &k).Done() ch := make(chan peer.PeerInfo) go func() { defer close(ch) request := pb.NewMessage(pb.Message_GET_PROVIDERS, string(k), 0) response, err := c.proxy.SendRequest(ctx, request) if err != nil { log.Debug(err) return } for _, p := range pb.PBPeersToPeerInfos(response.GetProviderPeers()) { select { case <-ctx.Done(): log.Debug(ctx.Err()) return case ch <- p: } } }() return ch }
// main roadmap: // - parse the commandline to get a cmdInvocation // - if user requests, help, print it and exit. // - run the command invocation // - output the response // - if anything fails, print error, maybe with help func main() { rand.Seed(time.Now().UnixNano()) runtime.GOMAXPROCS(3) // FIXME rm arbitrary choice for n ctx := logging.ContextWithLoggable(context.Background(), logging.Uuid("session")) var err error var invoc cmdInvocation defer invoc.close() // we'll call this local helper to output errors. // this is so we control how to print errors in one place. printErr := func(err error) { fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) } stopFunc, err := profileIfEnabled() if err != nil { printErr(err) os.Exit(1) } defer stopFunc() // to be executed as late as possible // this is a local helper to print out help text. // there's some considerations that this makes easier. printHelp := func(long bool, w io.Writer) { helpFunc := cmdsCli.ShortHelp if long { helpFunc = cmdsCli.LongHelp } helpFunc("ipfs", Root, invoc.path, w) } // this is a message to tell the user how to get the help text printMetaHelp := func(w io.Writer) { cmdPath := strings.Join(invoc.path, " ") fmt.Fprintf(w, "Use 'ipfs %s --help' for information about this command\n", cmdPath) } // Handle `ipfs help' if len(os.Args) == 2 && os.Args[1] == "help" { printHelp(false, os.Stdout) os.Exit(0) } // parse the commandline into a command invocation parseErr := invoc.Parse(ctx, os.Args[1:]) // BEFORE handling the parse error, if we have enough information // AND the user requested help, print it out and exit if invoc.req != nil { longH, shortH, err := invoc.requestedHelp() if err != nil { printErr(err) os.Exit(1) } if longH || shortH { printHelp(longH, os.Stdout) os.Exit(0) } } // ok now handle parse error (which means cli input was wrong, // e.g. incorrect number of args, or nonexistent subcommand) if parseErr != nil { printErr(parseErr) // this was a user error, print help. if invoc.cmd != nil { // we need a newline space. fmt.Fprintf(os.Stderr, "\n") printMetaHelp(os.Stderr) } os.Exit(1) } // here we handle the cases where // - commands with no Run func are invoked directly. // - the main command is invoked. if invoc.cmd == nil || invoc.cmd.Run == nil { printHelp(false, os.Stdout) os.Exit(0) } // ok, finally, run the command invocation. intrh, ctx := invoc.SetupInterruptHandler(ctx) defer intrh.Close() output, err := invoc.Run(ctx) if err != nil { printErr(err) // if this error was a client error, print short help too. if isClientError(err) { printMetaHelp(os.Stderr) } os.Exit(1) } // everything went better than expected :) _, err = io.Copy(os.Stdout, output) if err != nil { printErr(err) os.Exit(1) } }