Skip to content

Commit c3a6fae

Browse files
committed
staticaddr: serialize address creation with a channel
1 parent a1fb489 commit c3a6fae

File tree

1 file changed

+108
-3
lines changed

1 file changed

+108
-3
lines changed

staticaddr/address/manager.go

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,90 @@ type Manager struct {
5454
cfg *ManagerConfig
5555

5656
currentHeight atomic.Int32
57+
58+
// addrRequest is a channel used to request new static addresses from
59+
// the manager. The manager employs a go worker routine that handles the
60+
// requests.
61+
addrRequest chan request
62+
}
63+
64+
type request struct {
65+
ctx context.Context
66+
respChan chan response
67+
}
68+
69+
type response struct {
70+
addr *btcutil.AddressTaproot
71+
expiry int64
72+
err error
5773
}
5874

5975
// NewManager creates a new address manager.
6076
func NewManager(cfg *ManagerConfig, currentHeight int32) *Manager {
6177
m := &Manager{
62-
cfg: cfg,
78+
cfg: cfg,
79+
addrRequest: make(chan request),
6380
}
6481
m.currentHeight.Store(currentHeight)
6582

6683
return m
6784
}
6885

69-
// Run runs the address manager.
86+
// addrWorker is a worker that handles address creation requests. It calls
87+
// m.newAddress which blocks on server I/O and returns the address and expiry.
88+
func (m *Manager) addrWorker(ctx context.Context) {
89+
// We detect panics in the worker in recover() which in this case also
90+
// restores normal execution. We then return an error to the caller and
91+
// continue the address flow.
92+
panicRecovery := func(req request) {
93+
if r := recover(); r != nil {
94+
var err error
95+
if e, ok := r.(error); ok {
96+
err = e
97+
} else {
98+
err = fmt.Errorf("addrWorker panic: %v", r)
99+
}
100+
101+
// Best-effort notifies caller; don't block shutdown.
102+
select {
103+
case req.respChan <- response{err: err}:
104+
case <-req.ctx.Done():
105+
case <-ctx.Done():
106+
}
107+
}
108+
}
109+
110+
for {
111+
select {
112+
case req := <-m.addrRequest:
113+
func(req request) {
114+
// If the worker goroutine panics, we want to
115+
// recover and return an error to the caller.
116+
defer panicRecovery(req)
117+
118+
addr, expiry, e := m.newAddress(req.ctx)
119+
120+
resp := response{
121+
addr: addr,
122+
expiry: expiry,
123+
err: e,
124+
}
125+
126+
select {
127+
case req.respChan <- resp:
128+
case <-req.ctx.Done():
129+
case <-ctx.Done():
130+
}
131+
}(req)
132+
133+
case <-ctx.Done():
134+
return
135+
}
136+
}
137+
}
138+
139+
// Run runs the address manager. It keeps track of the current block height and
140+
// creates new static addresses as needed.
70141
func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error {
71142
newBlockChan, newBlockErrChan, err :=
72143
m.cfg.ChainNotifier.RegisterBlockEpochNtfn(ctx)
@@ -75,6 +146,10 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error {
75146
return err
76147
}
77148

149+
// The address worker offloads the I/O heavy address creation with the
150+
// server to a separate go routine.
151+
go m.addrWorker(ctx)
152+
78153
// Communicate to the caller that the address manager has completed its
79154
// initialization.
80155
close(initChan)
@@ -95,10 +170,40 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error {
95170
}
96171

97172
// NewAddress creates a new static address with the server or returns an
98-
// existing one.
173+
// existing one. It now sends a request to the manager's Run loop which
174+
// executes the actual address creation logic.
99175
func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
100176
int64, error) {
101177

178+
respChan := make(chan response, 1)
179+
req := request{
180+
ctx: ctx,
181+
respChan: respChan,
182+
}
183+
184+
// Send the new address request to the manager run loop.
185+
select {
186+
case m.addrRequest <- req:
187+
188+
case <-ctx.Done():
189+
return nil, 0, ctx.Err()
190+
}
191+
192+
// Wait for the response from the manager run loop.
193+
select {
194+
case resp := <-respChan:
195+
return resp.addr, resp.expiry, resp.err
196+
197+
case <-ctx.Done():
198+
return nil, 0, ctx.Err()
199+
}
200+
}
201+
202+
// newAddress contains the body of the former NewAddress method and performs the
203+
// actual address creation/lookup according to the requested type.
204+
func (m *Manager) newAddress(ctx context.Context) (*btcutil.AddressTaproot,
205+
int64, error) {
206+
102207
// If there's already a static address in the database, we can return
103208
// it.
104209
m.Lock()

0 commit comments

Comments
 (0)