-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lazy-start LogPoller from ContractReader, stop if started on shutdown #1051
Changes from all commits
5251c7f
9eec839
89c4306
c6da2cb
460258a
3fd4356
46288b8
13bb6d5
fd89dda
0c620e9
01d54f9
651178b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,8 @@ import ( | |
) | ||
|
||
type EventsReader interface { | ||
Start(ctx context.Context) error | ||
Ready() error | ||
RegisterFilter(context.Context, logpoller.Filter) error | ||
FilteredLogs(context.Context, []query.Expression, query.LimitAndSort, string) ([]logpoller.Log, error) | ||
} | ||
|
@@ -115,13 +117,25 @@ func (s *ContractReaderService) Name() string { | |
// and error. | ||
func (s *ContractReaderService) Start(ctx context.Context) error { | ||
return s.StartOnce(ServiceName, func() error { | ||
if len(s.filters) == 0 { | ||
// No dependency on EventReader | ||
return nil | ||
} | ||
if s.reader.Ready() != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be racy. Ready returns an error if the reader is starting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, this could fail if two ContractReader services both tried to start LogPoller at the same time.
Before implementing the above, I looked over the LogPoller startup code to see how feasible this would be. I think handling things there is a bit more messy and error prone. In part because there is a cascade of sub services started automatically by the engine. And in part because the first thing it does is load filters from the db. If it doesn't allow the db read to happen due to no filters being present, it will be deadlocked and unable to start. But if it does allow reading from the db, then suddenly we're introducing a lot of extra risk for the existing production OCR2 node ops, who are currently running without the Solana relay ever opening any db connections. The present version of the Solana relay they're running doesn't have access to a database at all, since it's not needed for DataFeeds. At least until we've fully e2e tested all this code I think it's safer for them to not have to access the database or start LogPoller. |
||
// Start EventReader if it hasn't already been | ||
// Lazily starting it here rather than earlier, since nodes running only ordinary DF jobs don't need it | ||
err := s.reader.Start(ctx) | ||
if err != nil && | ||
!strings.Contains(err.Error(), "has already been started") { // in case another thread calls Start() after Ready() returns | ||
return fmt.Errorf("%d event filters defined in ChainReader config, but unable to start event reader: %w", len(s.filters), err) | ||
} | ||
} | ||
// registering filters needs a context so we should be able to use the start function context. | ||
for _, filter := range s.filters { | ||
if err := s.reader.RegisterFilter(ctx, filter); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
@@ -533,8 +547,8 @@ func (s *ContractReaderService) addAddressResponseHardCoderModifier(namespace st | |
func (s *ContractReaderService) addEventRead( | ||
namespace, genericName string, | ||
contractAddress solana.PublicKey, | ||
_ codec.IDL, | ||
_ codec.IdlEvent, | ||
idl codec.IDL, | ||
eventIdl codec.IdlEvent, | ||
readDefinition config.ReadDefinition, | ||
events EventsReader, | ||
) error { | ||
|
@@ -546,7 +560,8 @@ func (s *ContractReaderService) addEventRead( | |
applyIndexedFieldTuple(mappedTuples, subKeys, readDefinition.IndexedField2, 2) | ||
applyIndexedFieldTuple(mappedTuples, subKeys, readDefinition.IndexedField3, 3) | ||
|
||
filter := toLPFilter(readDefinition.PollingFilter, contractAddress, subKeys[:]) | ||
filter := toLPFilter(readDefinition.PollingFilter, contractAddress, subKeys[:], | ||
codec.EventIDLTypes{Event: eventIdl, Types: idl.Types}) | ||
|
||
s.filters = append(s.filters, filter) | ||
s.bdRegistry.AddReadBinding(namespace, genericName, newEventReadBinding( | ||
|
@@ -565,12 +580,14 @@ func toLPFilter( | |
f *config.PollingFilter, | ||
address solana.PublicKey, | ||
subKeyPaths [][]string, | ||
eventIdl codec.EventIDLTypes, | ||
) logpoller.Filter { | ||
return logpoller.Filter{ | ||
Address: logpoller.PublicKey(address), | ||
EventName: f.EventName, | ||
EventSig: logpoller.EventSignature([]byte(f.EventName)[:logpoller.EventSignatureLength]), | ||
SubkeyPaths: logpoller.SubKeyPaths(subKeyPaths), | ||
EventSig: logpoller.NewEventSignatureFromName(f.EventName), | ||
EventIdl: logpoller.EventIdl(eventIdl), | ||
SubkeyPaths: subKeyPaths, | ||
Retention: f.Retention, | ||
MaxLogsKept: f.MaxLogsKept, | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens in ready throws an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only two things it can return are Unstarted or nil, so there are no other errors to handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I guess that's not quite right. It only returns one error, which means it's not in state==stateStarted, but there are a number of other possible states besides stateStarted and stateUnstarted it could be in:
I guess the only problematic one is stateStarting... which is sort of another way of saying what @dhaidashenko said, which is that it could be racy. We don't want to start it if it's already starting... even if it's not fully started yet.