Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE OR REPLACE FUNCTION backfill_account_advisory(rh_account_id_in INTEGER)
RETURNS VOID AS
$backfill$
BEGIN
PERFORM refresh_account_advisory_caches_multi(NULL, rh_account_id_in);
END;
$backfill$ LANGUAGE plpgsql;

SELECT drop_table_partition_triggers('account_advisory_sync_notified_insert',
$$BEFORE INSERT$$,
'account_advisory',
$$FOR EACH ROW EXECUTE PROCEDURE sync_account_advisory_notified_on_insert()$$);

DROP FUNCTION IF EXISTS sync_account_advisory_notified_on_insert();
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
CREATE OR REPLACE FUNCTION backfill_account_advisory(rh_account_id_in INTEGER)
RETURNS VOID AS
$backfill$
BEGIN
PERFORM refresh_account_advisory_caches_multi(NULL, rh_account_id_in);

-- copy `notified` for all `workspace_id`s per account
UPDATE account_advisory aa
SET notified = aad.notified
FROM advisory_account_data aad
WHERE aa.advisory_id = aad.advisory_id
AND aa.rh_account_id = aad.rh_account_id
AND aa.rh_account_id = rh_account_id_in
AND aad.notified IS NOT NULL;
END;
$backfill$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION sync_account_advisory_notified_on_insert()
RETURNS TRIGGER AS
$sync_notified_insert$
BEGIN
IF NEW.notified IS NULL THEN
SELECT notified INTO NEW.notified
FROM account_advisory
WHERE rh_account_id = NEW.rh_account_id
AND advisory_id = NEW.advisory_id
AND notified IS NOT NULL
LIMIT 1;
Comment on lines +22 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): A concurrent workspace insert can permanently retain a NULL notified value: the trigger reads existing account_advisory rows before the notification transaction updates them, while the notification UPDATE can finish before the new insert commits. The new row then remains unnotified even though advisory_account_data.notified and the existing workspace rows are set.

Triggers: When a new workspace for an already-notified advisory is inserted concurrently with markAdvisoriesNotified.

Suggested fix: Synchronize the insert and notification update with a locking or atomic database operation, or derive the inserted value from advisory_account_data.notified as well as existing account_advisory rows.

END IF;
RETURN NEW;
END;
$sync_notified_insert$ LANGUAGE plpgsql;

SELECT create_table_partition_triggers('account_advisory_sync_notified_insert',
$$BEFORE INSERT$$,
'account_advisory',
$$FOR EACH ROW EXECUTE PROCEDURE sync_account_advisory_notified_on_insert()$$);
33 changes: 32 additions & 1 deletion database_admin/schema/create_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations


INSERT INTO schema_migrations
VALUES (167, false);
VALUES (168, false);

-- ---------------------------------------------------------------------------
-- Functions
Expand Down Expand Up @@ -229,9 +229,35 @@ CREATE OR REPLACE FUNCTION backfill_account_advisory(rh_account_id_in INTEGER)
$backfill$
BEGIN
PERFORM refresh_account_advisory_caches_multi(NULL, rh_account_id_in);

-- copy `notified` for all `workspace_id`s per account
UPDATE account_advisory aa
SET notified = aad.notified
FROM advisory_account_data aad
WHERE aa.advisory_id = aad.advisory_id
AND aa.rh_account_id = aad.rh_account_id
AND aa.rh_account_id = rh_account_id_in
AND aad.notified IS NOT NULL;
END;
$backfill$ LANGUAGE plpgsql;

-- handle a new workspace with already notified advisory
CREATE OR REPLACE FUNCTION sync_account_advisory_notified_on_insert()
RETURNS TRIGGER AS
$sync_notified_insert$
BEGIN
IF NEW.notified IS NULL THEN
SELECT notified INTO NEW.notified
FROM account_advisory
WHERE rh_account_id = NEW.rh_account_id
AND advisory_id = NEW.advisory_id
AND notified IS NOT NULL
LIMIT 1;
END IF;
RETURN NEW;
END;
$sync_notified_insert$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION refresh_system_caches(system_id_in BIGINT DEFAULT NULL,
rh_account_id_in INTEGER DEFAULT NULL)
RETURNS INTEGER AS
Expand Down Expand Up @@ -901,6 +927,11 @@ SELECT grant_table_partitions('SELECT, INSERT, UPDATE, DELETE', 'account_advisor
SELECT grant_table_partitions('SELECT, INSERT, UPDATE, DELETE', 'account_advisory', 'listener');
SELECT grant_table_partitions('SELECT, INSERT, UPDATE, DELETE', 'account_advisory', 'vmaas_sync');

SELECT create_table_partition_triggers('account_advisory_sync_notified_insert',
$$BEFORE INSERT$$,
'account_advisory',
$$FOR EACH ROW EXECUTE PROCEDURE sync_account_advisory_notified_on_insert()$$);

CREATE INDEX ON account_advisory (systems_applicable);
CREATE INDEX ON account_advisory (systems_installable);
CREATE INDEX ON account_advisory (advisory_id);
Expand Down
7 changes: 7 additions & 0 deletions evaluator/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ func markAdvisoriesNotified(tx *gorm.DB, accountID int, advisoryIDs []int64) err
if err != nil {
return errors.Wrap(err, "updating notified column failed")
}
// Ensure notifications are in sync between aad and aa, while we transition
err = tx.Table("account_advisory").
Where("rh_account_id = ? AND advisory_id IN (?)", accountID, advisoryIDs).
Update("notified", time.Now()).Error
if err != nil {
return errors.Wrap(err, "updating notified column in account_advisory failed")
}
return nil
}

Expand Down
Loading