Carry the event's receipt time into every delivery (closes #257)
All checks were successful
check / check (push) Successful in 3m4s
All checks were successful
check / check (push) Successful in 3m4s
This commit was merged in pull request #297.
This commit is contained in:
@@ -440,7 +440,7 @@ func (e *Engine) processNewTask(
|
||||
|
||||
event := buildEventFromTask(task)
|
||||
|
||||
event, err = e.resolveEventBody(
|
||||
event, err = e.hydrateEvent(
|
||||
webhookDB, event, task,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -512,7 +512,7 @@ func (e *Engine) processRetryTask(
|
||||
|
||||
event := buildEventFromTask(task)
|
||||
|
||||
event, err = e.resolveEventBody(
|
||||
event, err = e.hydrateEvent(
|
||||
webhookDB, event, task,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1547,6 +1547,11 @@ func truncate(s string, maxLen int) string {
|
||||
|
||||
// --- Helper functions ---
|
||||
|
||||
// buildEventFromTask reconstructs the event a Task describes, as far
|
||||
// as the Task itself goes. The fields it cannot fill — the body when
|
||||
// it was too large to inline, and the receipt time, which no Task
|
||||
// carries — come from the stored row in hydrateEvent, which every
|
||||
// caller of this function runs next.
|
||||
func buildEventFromTask(task *Task) database.Event {
|
||||
event := database.Event{
|
||||
EntrypointID: task.EntrypointID,
|
||||
@@ -1574,29 +1579,67 @@ func buildTargetFromTask(task *Task) database.Target {
|
||||
return target
|
||||
}
|
||||
|
||||
func (e *Engine) resolveEventBody(
|
||||
// hydrateEvent fills in the event fields a Task does not carry, by
|
||||
// reading the stored event row.
|
||||
//
|
||||
// CreatedAt is the event's receipt time and lives only in that row.
|
||||
// The Slack target renders it into every message it sends, so an
|
||||
// unhydrated event puts the zero time in front of a human on every
|
||||
// notification the product delivers. See
|
||||
// https://git.eeqj.de/sneak/webhooker/issues/257.
|
||||
//
|
||||
// The body comes from the same row when the Task did not inline it,
|
||||
// which is the case for a body at or above MaxInlineBodySize.
|
||||
//
|
||||
// A read failure is fatal to the delivery only when the body depended
|
||||
// on it. When the Task inlined the body, the delivery has everything
|
||||
// it needs to be sent and goes ahead with the timestamp unset: the row
|
||||
// can be gone under a retention reap while a queued delivery still
|
||||
// holds its body, and dropping a deliverable event to protect one
|
||||
// metadata field would be a worse failure than the one it prevents.
|
||||
func (e *Engine) hydrateEvent(
|
||||
webhookDB *gorm.DB,
|
||||
event database.Event,
|
||||
task *Task,
|
||||
) (database.Event, error) {
|
||||
if task.Body != nil {
|
||||
columns := []string{"created_at"}
|
||||
|
||||
if task.Body == nil {
|
||||
columns = append(columns, "body")
|
||||
}
|
||||
|
||||
var dbEvent database.Event
|
||||
|
||||
err := webhookDB.Select(columns).
|
||||
First(&dbEvent, "id = ?", task.EventID).Error
|
||||
if err != nil {
|
||||
if task.Body == nil {
|
||||
return event, fmt.Errorf(
|
||||
"fetching event body: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
e.log.Warn(
|
||||
"could not read the stored event; delivering "+
|
||||
"the inlined body without its receipt time",
|
||||
"event_id", task.EventID,
|
||||
"delivery_id", task.DeliveryID,
|
||||
"error", err,
|
||||
)
|
||||
|
||||
event.Body = *task.Body
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
var dbEvent database.Event
|
||||
event.CreatedAt = dbEvent.CreatedAt
|
||||
|
||||
err := webhookDB.Select("body").
|
||||
First(&dbEvent, "id = ?", task.EventID).Error
|
||||
if err != nil {
|
||||
return event, fmt.Errorf(
|
||||
"fetching event body: %w", err,
|
||||
)
|
||||
if task.Body != nil {
|
||||
event.Body = *task.Body
|
||||
} else {
|
||||
event.Body = dbEvent.Body
|
||||
}
|
||||
|
||||
event.Body = dbEvent.Body
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user