mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-05 19:11:00 +08:00
### Background In #36061, dependent site settings were introduced through the `depends_on` key. This is used internally to construct a dependency graph and sort settings in order-of-dependency before bulk updates. However, no safeguards were introduced to avoid circular dependencies. This means a `TSort::Cyclic` could come along and ruin the day at arbitrary points during runtime. ### What is this change? This change does two things: 1. Rescue, format, and re-raise `TSort::Cyclic` exceptions. 2. Eagerly order settings on app reload to catch cyclic dependencies up-front.
69 lines
1.4 KiB
Ruby
69 lines
1.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module SiteSettings
|
|
end
|
|
|
|
class SiteSettings::DependencyGraph
|
|
include TSort
|
|
|
|
CircularDependency = Class.new(StandardError)
|
|
|
|
attr_reader :dependencies, :behaviors
|
|
|
|
def initialize(dependencies = {})
|
|
@dependencies = dependencies
|
|
@behaviors = {}
|
|
end
|
|
|
|
def []=(setting, value)
|
|
dependencies[setting] = value
|
|
end
|
|
|
|
def [](setting)
|
|
dependencies[setting]
|
|
end
|
|
|
|
def reverse_dependencies
|
|
@reverse_dependencies ||=
|
|
begin
|
|
rev = {}
|
|
dependencies.each do |setting, deps|
|
|
Array(deps).each { |dep| (rev[dep.to_s] ||= []) << setting }
|
|
end
|
|
rev
|
|
end
|
|
end
|
|
|
|
def dependents(setting)
|
|
reverse_dependencies.fetch(setting.to_s, [])
|
|
end
|
|
|
|
def change_behavior(setting, behavior)
|
|
behavior = behavior.to_sym
|
|
raise ArgumentError.new("Behavior must be :hidden") unless behavior == :hidden
|
|
behaviors[setting] = behavior
|
|
end
|
|
|
|
def order
|
|
@order ||= tsort
|
|
rescue TSort::Cyclic
|
|
cycles =
|
|
strongly_connected_components.reject(&:one?).map { |cycle| cycle.join(" <-> ") }.join("\n")
|
|
|
|
raise CircularDependency.new(<<~MESSAGE)
|
|
Circular dependencies in site settings:
|
|
|
|
#{cycles}
|
|
MESSAGE
|
|
end
|
|
|
|
private
|
|
|
|
def tsort_each_child(node, &block)
|
|
dependencies.fetch(node, []).each(&block)
|
|
end
|
|
|
|
def tsort_each_node(&block)
|
|
dependencies.each_key(&block)
|
|
end
|
|
end
|