Skip to content

Commit 427e828

Browse files
martin-brennanyuriyaran
authored andcommitted
FIX: Theme site settings not reloading across processes (#34242)
This commit fixes an issue in multi-process setups where the following would happen: 1. Admin changes a theme site setting. The new value is stored in memory in the process that made the change. For this example, changing `enable_welcome_banner` from `true` to `false` 2. Another user visits the site and hits another process, which has the old value of `enable_welcome_banner` still set to `true` in memory 3. The other user's value for `enable_welcome_banner` is cached in the cache for theme site settings, which is shared across all processes The issue is fixed by doing the same thing as site settings: when the new value is saved, we send a MessageBus message to all processes via `SiteSettingExtension` which calls `refresh!` and reloads the in-memory cache of theme site settings from the DB. c.f. https://meta.discourse.org/t/difficulty-understanding-the-enable-welcome-banner-setting/377321
1 parent b266dfa commit 427e828

File tree

5 files changed

+86
-4
lines changed

5 files changed

+86
-4
lines changed

app/models/remote_theme.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ def create_theme_site_settings(theme, theme_site_settings)
484484
value: value,
485485
},
486486
guardian: Discourse.system_user.guardian,
487-
) { |result| }
487+
)
488488
end
489489

490490
SiteSetting.refresh!(refresh_site_settings: false, refresh_theme_site_settings: true)

lib/site_setting_extension.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def theme_site_settings_json(theme_id)
177177
end
178178

179179
def setting_metadata_hash(setting)
180-
setting_hash = {
180+
{
181181
setting:,
182182
default: SiteSetting.defaults[setting],
183183
description: SiteSetting.description(setting),
@@ -433,6 +433,7 @@ def refresh!(refresh_site_settings: true, refresh_theme_site_settings: true)
433433

434434
theme_site_setting_changes, theme_site_setting_deletions =
435435
diff_hash(new_theme_site_settings, theme_site_settings)
436+
436437
theme_site_setting_changes.each do |theme_id, settings|
437438
theme_site_settings[theme_id] ||= {}
438439
theme_site_settings[theme_id].merge!(settings)
@@ -599,6 +600,7 @@ def change_themeable_site_setting(theme_id, name, val)
599600
theme_site_settings[theme_id][name] = val
600601

601602
notify_clients!(name, theme_id: theme_id) if client_settings.include?(name)
603+
notify_changed!
602604

603605
clear_cache!(expire_theme_site_setting_cache: true)
604606

lib/site_settings/local_process_provider.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def initialize(name, data_type)
2121
end
2222
end
2323

24+
attr_accessor :model
25+
2426
def settings
2527
@settings[current_site] ||= {}
2628
end
@@ -46,6 +48,11 @@ def save(name, value, data_type)
4648
settings[name] = setting
4749
end
4850
setting.value = value.to_s
51+
52+
# So we can simulate the MessageBus notifications
53+
# done by the DbProvider
54+
@model.notify_changed! if @model
55+
4956
setting
5057
end
5158

spec/lib/site_setting_extension_spec.rb

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@
4141
new_settings(provider_local)
4242
end
4343

44-
it "Does not leak state cause changes are not linked" do
44+
after do
45+
settings.provider.model = nil
46+
settings.listen_for_changes = false
47+
settings2.listen_for_changes = false
48+
end
49+
50+
it "does not leak state cause changes are not linked" do
4551
t1 =
4652
Thread.new do
4753
5.times do
@@ -89,7 +95,7 @@
8995
expect(settings.hello).to eq(99)
9096
end
9197

92-
it "publishes changes cross sites" do
98+
it "picks up changes from provider on refresh across processes" do
9399
settings.setting(:hello, 1)
94100
settings2.setting(:hello, 1)
95101

@@ -104,6 +110,30 @@
104110
expect(settings2.hello).to eq(99)
105111
end
106112

113+
it "publishes changes across processes" do
114+
MessageBus.on
115+
116+
# Only need to do this once, since both settings will use the same provider. Doing it twice leads to confusion with MessageBus
117+
settings.provider.model = settings
118+
119+
settings.listen_for_changes = true
120+
settings2.listen_for_changes = true
121+
122+
settings.refresh!
123+
settings2.refresh!
124+
125+
settings.setting(:hello, 1)
126+
settings2.setting(:hello, 1)
127+
128+
settings.hello = 100
129+
130+
expect(settings2.hello).to eq(100)
131+
132+
settings.hello = 99
133+
134+
expect(settings2.hello).to eq(99)
135+
end
136+
107137
it "does not override types in the type supervisor" do
108138
settings.setting(:foo, "bar")
109139
settings.provider.save(:foo, "bar", SiteSetting.types[:enum])
@@ -1048,6 +1078,38 @@ def self.translate_names?
10481078
expect(SiteSetting.search_experience(theme_id: theme_2.id)).to eq("search_field")
10491079
end
10501080

1081+
it "publishes changes across processes" do
1082+
MessageBus.on
1083+
1084+
settings_tss_instance_1 = new_settings(provider_local)
1085+
settings_tss_instance_2 = new_settings(provider_local)
1086+
1087+
settings_tss_instance_1.listen_for_changes = true
1088+
settings_tss_instance_2.listen_for_changes = true
1089+
1090+
settings_tss_instance_1.load_settings(File.join(Rails.root, "config", "site_settings.yml"))
1091+
settings_tss_instance_2.load_settings(File.join(Rails.root, "config", "site_settings.yml"))
1092+
1093+
settings_tss_instance_1.refresh!
1094+
settings_tss_instance_2.refresh!
1095+
1096+
expect(settings_tss_instance_1.enable_welcome_banner(theme_id: theme_1.id)).to eq(false)
1097+
expect(settings_tss_instance_2.enable_welcome_banner(theme_id: theme_1.id)).to eq(false)
1098+
1099+
tss_1.update!(value: true)
1100+
settings_tss_instance_1.change_themeable_site_setting(
1101+
theme_1.id,
1102+
:enable_welcome_banner,
1103+
true,
1104+
)
1105+
1106+
# Get through the MessageBus queue
1107+
try_until_success(frequency: 0.5) do
1108+
expect(settings_tss_instance_1.enable_welcome_banner(theme_id: theme_1.id)).to eq(true)
1109+
expect(settings_tss_instance_2.enable_welcome_banner(theme_id: theme_1.id)).to eq(true)
1110+
end
1111+
end
1112+
10511113
describe ".theme_site_settings_json_uncached" do
10521114
it "returns the correct JSON" do
10531115
SiteSetting.refresh!

spec/support/helpers.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,17 @@ def enable_current_plugin
318318
SiteSetting.public_send("#{plugin.enabled_site_setting}=", true)
319319
end
320320

321+
def try_until_success(timeout: 3, frequency: 0.01)
322+
start ||= Time.zone.now
323+
backoff ||= frequency
324+
yield
325+
rescue RSpec::Expectations::ExpectationNotMetError
326+
raise if Time.zone.now >= start + timeout.seconds
327+
sleep backoff
328+
backoff += frequency
329+
retry
330+
end
331+
321332
private
322333

323334
def directory_from_caller

0 commit comments

Comments
 (0)