-
Notifications
You must be signed in to change notification settings - Fork 19
fix: load missed events not working properly #276
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import { EventService } from './event.service'; | |
|
|
||
| import { LockRepository } from '../repositories/lock.repository'; | ||
| import { ConfigService } from './config.service'; | ||
| import { FederationService } from './federation.service'; | ||
| import { MissingEventService } from './missing-event.service'; | ||
| import { PartialStateResolutionError, StateService } from './state.service'; | ||
|
|
||
|
|
@@ -32,6 +33,13 @@ class MissingAuthorizationEventsError extends Error { | |
| } | ||
| } | ||
|
|
||
| class MissingEventsError extends Error { | ||
| constructor(message: string) { | ||
| super(message); | ||
| this.name = 'MissingEventsError'; | ||
| } | ||
| } | ||
|
|
||
| @singleton() | ||
| export class StagingAreaService { | ||
| private readonly logger = createLogger('StagingAreaService'); | ||
|
|
@@ -43,6 +51,7 @@ export class StagingAreaService { | |
| private readonly eventAuthService: EventAuthorizationService, | ||
| private readonly eventEmitterService: EventEmitterService, | ||
| private readonly stateService: StateService, | ||
| private readonly federationService: FederationService, | ||
| private readonly lockRepository: LockRepository, | ||
| ) {} | ||
|
|
||
|
|
@@ -83,7 +92,13 @@ export class StagingAreaService { | |
| this.logger.info({ msg: 'Processing event', eventId: event._id }); | ||
|
|
||
| try { | ||
| await this.processDependencyStage(event); | ||
| const addedMissing = await this.processDependencyStage(event); | ||
| if (addedMissing) { | ||
| // if we added missing events, we postpone the processing of this event | ||
| // to give time for the missing events to be processed first | ||
| throw new MissingEventsError('Added missing events'); | ||
| } | ||
|
|
||
| if ('from' in event && event.from !== 'join') { | ||
| await this.processAuthorizationStage(event); | ||
| } | ||
|
|
@@ -106,6 +121,11 @@ export class StagingAreaService { | |
| eventId: event._id, | ||
| err, | ||
| }); | ||
| } else if (err instanceof MissingEventsError) { | ||
| this.logger.info({ | ||
| msg: 'Added missing events, postponing current event processing', | ||
| eventId: event._id, | ||
| }); | ||
| } else { | ||
| this.logger.error({ | ||
| msg: 'Error processing event', | ||
|
|
@@ -155,31 +175,67 @@ export class StagingAreaService { | |
| ); | ||
|
|
||
| if (missing.length === 0) { | ||
| return; | ||
| return false; | ||
| } | ||
| this.logger.debug(`Missing ${missing.length} events for ${eventId}`); | ||
|
|
||
| const found = await Promise.all( | ||
| missing.map((missingId) => { | ||
| this.logger.debug( | ||
| `Adding missing event ${missingId} to missing events service`, | ||
| ); | ||
| this.logger.debug( | ||
| `Missing ${missing.length} events for ${eventId}: ${missing}`, | ||
| ); | ||
|
|
||
| return this.missingEventsService.fetchMissingEvent({ | ||
| eventId: missingId, | ||
| roomId: event.event.room_id, | ||
| origin: event.origin, | ||
| }); | ||
| }), | ||
| const latestEvent = await this.eventService.getLastEventForRoom( | ||
| event.event.room_id, | ||
| ); | ||
|
|
||
| const addedMissing = found.some((f) => f === true); | ||
| let addedMissing = false; | ||
|
|
||
| if (latestEvent) { | ||
| this.logger.debug( | ||
| `Fetching missing events between ${latestEvent._id} and ${eventId} for room ${event.event.room_id}`, | ||
| ); | ||
|
|
||
| const missingEvents = await this.federationService.getMissingEvents( | ||
| event.origin, | ||
| event.event.room_id, | ||
| [latestEvent._id], | ||
| [eventId], | ||
| 10, | ||
| 0, | ||
| ); | ||
|
|
||
| this.logger.debug( | ||
| `Persisting ${missingEvents.events.length} fetched missing events`, | ||
| ); | ||
|
|
||
| await this.eventService.processIncomingPDUs( | ||
| event.origin, | ||
| missingEvents.events, | ||
| ); | ||
|
|
||
| addedMissing = missingEvents.events.length > 0; | ||
| } else { | ||
|
Comment on lines
+208
to
+214
Contributor
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. Prevent re-staging the current event via When 🤖 Prompt for AI Agents |
||
| const found = await Promise.all( | ||
| missing.map((missingId) => { | ||
| this.logger.debug( | ||
| `Adding missing event ${missingId} to missing events service`, | ||
| ); | ||
|
|
||
| return this.missingEventsService.fetchMissingEvent({ | ||
| eventId: missingId, | ||
| roomId: event.event.room_id, | ||
| origin: event.origin, | ||
| }); | ||
| }), | ||
| ); | ||
|
|
||
| addedMissing = found.some((f) => f === true); | ||
| } | ||
|
|
||
| // if the auth events are missing, the authorization stage will fail anyway, | ||
| // so to save some time we throw an error here, and the event processing will be postponed | ||
| if (addedMissing && authEvents.some((e) => missing.includes(e))) { | ||
| throw new MissingAuthorizationEventsError('Missing authorization events'); | ||
| } | ||
|
|
||
| return addedMissing; | ||
| } | ||
|
|
||
| private async processAuthorizationStage(event: EventStagingStore) { | ||
|
|
||
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.
Handle remote fetch failures without dropping the staged event.
If
federationService.getMissingEventsthrows (network error, 4xx/5xx, etc.), the exception bubbles up toprocessEventForRoom, falls into the genericelsebranch, logs an error, and the event is un-staged viaeventService.markEventAsUnstaged(event). That means we permanently discard the event just because fetching its dependencies failed once.We should avoid unstaging in this case—either catch the error here and return
false(so processing is retried later) or rethrow a dedicated error handled like the other postponement cases.🤖 Prompt for AI Agents