From aac995714b8e336f414613d9c7c5871d002e990a Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 21 Jul 2026 18:51:41 -0300 Subject: [PATCH] timeout relay connections at 2s. --- count.go | 19 ++++++--- event.go | 16 ++++++-- group.go | 72 +++++++++++++++++++++++++-------- helpers.go | 98 +++++++++++++++++++++++++-------------------- nostrfs_cgo/root.go | 5 ++- 5 files changed, 142 insertions(+), 68 deletions(-) diff --git a/count.go b/count.go index 8f14183..d4c0993 100644 --- a/count.go +++ b/count.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "strings" + "time" "fiatjaf.com/nostr" "fiatjaf.com/nostr/nip45" @@ -63,11 +64,19 @@ var count = &cli.Command{ hll = hyperloglog.New(offset) } for _, relayUrl := range relayUrls { - relay, err := sys.Pool.EnsureRelay(relayUrl) - if err != nil { - fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl) - fmt.Fprintf(os.Stderr, "error: %s\n", err) - continue + nm := nostr.NormalizeURL(relayUrl) + relay, ok := sys.Pool.Relays.Load(nm) + if !ok || relay == nil || !relay.IsConnected() { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + var err error + relay, err = nostr.RelayConnect(ct, relayUrl, sys.Pool.RelayOptions) + cancel() + if err != nil { + fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl) + fmt.Fprintf(os.Stderr, "error: %s\n", err) + continue + } + sys.Pool.Relays.Store(nm, relay) } count, hllRegisters, err := relay.Count(ctx, filter, nostr.SubscriptionOptions{ diff --git a/event.go b/event.go index 2e8ebf9..b62137d 100644 --- a/event.go +++ b/event.go @@ -543,9 +543,19 @@ func publishFlow(ctx context.Context, c *cli.Command, kr nostr.Signer, evt nostr defer cancel() if !relay.IsConnected() { - if new_, err := sys.Pool.EnsureRelay(relay.URL); err == nil { - relays[i] = new_ - relay = new_ + nm := nostr.NormalizeURL(relay.URL) + if r, ok := sys.Pool.Relays.Load(nm); ok && r != nil && r.IsConnected() { + relays[i] = r + relay = r + } else { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + new_, err := nostr.RelayConnect(ct, relay.URL, sys.Pool.RelayOptions) + cancel() + if err == nil { + sys.Pool.Relays.Store(nm, new_) + relays[i] = new_ + relay = new_ + } } } diff --git a/group.go b/group.go index 37ea1a0..d5918ac 100644 --- a/group.go +++ b/group.go @@ -243,9 +243,17 @@ var group = &cli.Command{ return err } - r, err := sys.Pool.EnsureRelay(relay) - if err != nil { - return err + nm := nostr.NormalizeURL(relay) + r, ok := sys.Pool.Relays.Load(nm) + if !ok || r == nil || !r.IsConnected() { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + var err error + r, err = nostr.RelayConnect(ct, relay, sys.Pool.RelayOptions) + cancel() + if err != nil { + return err + } + sys.Pool.Relays.Store(nm, r) } sub, err := r.Subscribe(ctx, nostr.Filter{ @@ -316,11 +324,19 @@ var group = &cli.Command{ return fmt.Errorf("failed to sign message: %w", err) } - if r, err := sys.Pool.EnsureRelay(relay); err != nil { - return err - } else { - return r.Publish(ctx, msg) + nm := nostr.NormalizeURL(relay) + r, ok := sys.Pool.Relays.Load(nm) + if !ok || r == nil || !r.IsConnected() { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + var err error + r, err = nostr.RelayConnect(ct, relay, sys.Pool.RelayOptions) + cancel() + if err != nil { + return err + } + sys.Pool.Relays.Store(nm, r) } + return r.Publish(ctx, msg) }, }, }, @@ -474,9 +490,17 @@ write your forum post return fmt.Errorf("failed to sign forum topic event: %w", err) } - r, err := sys.Pool.EnsureRelay(relay) - if err != nil { - return err + nm := nostr.NormalizeURL(relay) + r, ok := sys.Pool.Relays.Load(nm) + if !ok || r == nil || !r.IsConnected() { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + var err error + r, err = nostr.RelayConnect(ct, relay, sys.Pool.RelayOptions) + cancel() + if err != nil { + return err + } + sys.Pool.Relays.Store(nm, r) } return r.Publish(ctx, evt) @@ -581,9 +605,17 @@ write your forum post return fmt.Errorf("failed to sign forum comment event: %w", err) } - r, err := sys.Pool.EnsureRelay(relay) - if err != nil { - return err + nm := nostr.NormalizeURL(relay) + r, ok := sys.Pool.Relays.Load(nm) + if !ok || r == nil || !r.IsConnected() { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + var err error + r, err = nostr.RelayConnect(ct, relay, sys.Pool.RelayOptions) + cancel() + if err != nil { + return err + } + sys.Pool.Relays.Store(nm, r) } return r.Publish(ctx, evt) @@ -906,9 +938,17 @@ func publishModerationEvent(ctx context.Context, c *cli.Command, kind nostr.Kind stdout(evt.String()) - r, err := sys.Pool.EnsureRelay(relay) - if err != nil { - return err + nm := nostr.NormalizeURL(relay) + r, ok := sys.Pool.Relays.Load(nm) + if !ok || r == nil || !r.IsConnected() { + ct, cancel := context.WithTimeout(context.Background(), 2*time.Second) + var err error + r, err = nostr.RelayConnect(ct, relay, sys.Pool.RelayOptions) + cancel() + if err != nil { + return err + } + sys.Pool.Relays.Store(nm, r) } return r.Publish(ctx, evt) diff --git a/helpers.go b/helpers.go index 524513f..5adbffc 100644 --- a/helpers.go +++ b/helpers.go @@ -268,63 +268,75 @@ func connectToSingleRelay( colorizepreamble func(c func(string, ...any) string), logthis func(s string, args ...any), ) *nostr.Relay { - if relay, err := sys.Pool.EnsureRelay(url); err == nil { - if c.Bool("force-pre-auth") { - if colorizepreamble != nil { - colorizepreamble(color.YellowString) - } - logthis("waiting for auth challenge... ") - time.Sleep(time.Millisecond * 200) + nm := nostr.NormalizeURL(url) - for range 5 { - if err := relay.Auth(ctx, func(ctx context.Context, authEvent *nostr.Event) error { - challengeTag := authEvent.Tags.Find("challenge") - if challengeTag == nil || len(challengeTag) < 2 || challengeTag[1] == "" { - return fmt.Errorf("auth not received yet *****") // what a giant hack - } - return authSigner(ctx, c, logthis, authEvent) - }); err == nil { - // auth succeeded - goto preauthSuccess - } else { - // auth failed - if strings.HasSuffix(err.Error(), "auth not received yet *****") { - // it failed because we didn't receive the challenge yet, so keep waiting - time.Sleep(time.Second) - continue - } else { - // it failed for some other reason, so skip this relay - if colorizepreamble != nil { - colorizepreamble(colors.errorf) - } - logthis(err.Error()) - return nil - } - } - } + relay, ok := sys.Pool.Relays.Load(nm) + if !ok || relay == nil || !relay.IsConnected() { + connectCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + var err error + if relay, err = nostr.RelayConnect(connectCtx, url, sys.Pool.RelayOptions); err != nil { if colorizepreamble != nil { colorizepreamble(colors.errorf) } - logthis("failed to get an AUTH challenge in enough time.") + logthis(clampError(err, len(url)+12)) return nil } - preauthSuccess: + sys.Pool.Relays.Store(nm, relay) + go func(r *nostr.Relay, relayURL string) { + <-r.Context().Done() + if current, ok := sys.Pool.Relays.Load(relayURL); ok && current == r { + sys.Pool.Relays.Delete(relayURL) + } + }(relay, nm) + } + + if c.Bool("force-pre-auth") { if colorizepreamble != nil { - colorizepreamble(colors.successf) + colorizepreamble(color.YellowString) + } + logthis("waiting for auth challenge... ") + time.Sleep(time.Millisecond * 200) + + for range 5 { + if err := relay.Auth(ctx, func(ctx context.Context, authEvent *nostr.Event) error { + challengeTag := authEvent.Tags.Find("challenge") + if challengeTag == nil || len(challengeTag) < 2 || challengeTag[1] == "" { + return fmt.Errorf("auth not received yet *****") // what a giant hack + } + return authSigner(ctx, c, logthis, authEvent) + }); err == nil { + // auth succeeded + goto preauthSuccess + } else { + // auth failed + if strings.HasSuffix(err.Error(), "auth not received yet *****") { + time.Sleep(time.Second) + continue + } else { + if colorizepreamble != nil { + colorizepreamble(colors.errorf) + } + logthis(err.Error()) + return nil + } + } } - logthis("ok.") - return relay - } else { if colorizepreamble != nil { colorizepreamble(colors.errorf) } - - // if we're here that means we've failed to connect, this may be a huge message - // but we're likely to only be interested in the lowest level error (although we can leave space) - logthis(clampError(err, len(url)+12)) + logthis("failed to get an AUTH challenge in enough time.") return nil } + +preauthSuccess: + if colorizepreamble != nil { + colorizepreamble(colors.successf) + } + logthis("ok.") + return relay } func clearLines(lineCount int) { diff --git a/nostrfs_cgo/root.go b/nostrfs_cgo/root.go index fed3bae..04eb393 100644 --- a/nostrfs_cgo/root.go +++ b/nostrfs_cgo/root.go @@ -996,10 +996,13 @@ func (r *NostrRoot) publishNote(path string) { } for _, url := range relays { - relay, err := r.sys.Pool.EnsureRelay(url) + connectCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + relay, err := nostr.RelayConnect(connectCtx, url, r.sys.Pool.RelayOptions) + cancel() if err != nil { continue } + r.sys.Pool.Relays.Store(nostr.NormalizeURL(url), relay) relay.Publish(ctx, *evt) }