Skip to content

Commit a3157a4

Browse files
authored
Atomically update feature flags at runtime (#10281)
1 parent 881e575 commit a3157a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+886
-51
lines changed

ydb/core/base/appdata.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <ydb/core/protos/netclassifier.pb.h>
2626
#include <ydb/core/protos/datashard_config.pb.h>
2727
#include <ydb/core/protos/shared_cache.pb.h>
28+
#include <ydb/core/protos/feature_flags.pb.h>
2829
#include <ydb/library/pdisk_io/aio.h>
2930

3031
#include <ydb/library/actors/interconnect/poller_tcp.h>
@@ -125,6 +126,14 @@ TAppData::TAppData(
125126
TAppData::~TAppData()
126127
{}
127128

129+
void TAppData::InitFeatureFlags(const NKikimrConfig::TFeatureFlags& flags) {
130+
Impl->FeatureFlags = flags;
131+
}
132+
133+
void TAppData::UpdateRuntimeFlags(const NKikimrConfig::TFeatureFlags& flags) {
134+
Impl->FeatureFlags.CopyRuntimeFrom(flags);
135+
}
136+
128137
TIntrusivePtr<IRandomProvider> TAppData::RandomProvider = CreateDefaultRandomProvider();
129138
TIntrusivePtr<ITimeProvider> TAppData::TimeProvider = CreateDefaultTimeProvider();
130139

ydb/core/base/appdata_fwd.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ namespace NKikimrConfig {
6464
class TGraphConfig;
6565
class TMetadataCacheConfig;
6666
class TMemoryControllerConfig;
67+
class TFeatureFlags;
6768
}
6869

6970
namespace NKikimrReplication {
@@ -271,6 +272,9 @@ struct TAppData {
271272
TProgramShouldContinue *kikimrShouldContinue);
272273

273274
~TAppData();
275+
276+
void InitFeatureFlags(const NKikimrConfig::TFeatureFlags& flags);
277+
void UpdateRuntimeFlags(const NKikimrConfig::TFeatureFlags& flags);
274278
};
275279

276280
inline TAppData* AppData(NActors::TActorSystem* actorSystem) {

ydb/core/base/events.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ struct TKikimrEvents : TEvents {
182182
ES_MEMORY = 4259,
183183
ES_GROUPED_ALLOCATIONS_MANAGER = 4260,
184184
ES_INCREMENTAL_RESTORE_SCAN = 4261,
185+
ES_FEATURE_FLAGS = 4262,
185186
};
186187
};
187188

ydb/core/base/feature_flags.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#pragma once
2-
32
#include "defs.h"
4-
5-
#include <ydb/core/protos/feature_flags.pb.h>
3+
#include "runtime_feature_flags.h"
64

75
namespace NKikimr {
86

9-
class TFeatureFlags: public NKikimrConfig::TFeatureFlags {
10-
using TBase = NKikimrConfig::TFeatureFlags;
7+
/**
8+
* Thin wrapper around TRuntimeFeatureFlags, for compatibility with existing code
9+
*/
10+
class TFeatureFlags : public TRuntimeFeatureFlags {
11+
using TBase = TRuntimeFeatureFlags;
1112

1213
public:
1314
using TBase::TBase;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "feature_flags_service.h"
2+
3+
namespace NKikimr {
4+
5+
TActorId MakeFeatureFlagsServiceID() {
6+
return TActorId(ui32(0), "featureflags");
7+
}
8+
9+
} // namespace NKikimr

ydb/core/base/feature_flags_service.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
#include "defs.h"
3+
#include "events.h"
4+
5+
namespace NKikimr {
6+
7+
struct TEvFeatureFlags {
8+
enum EEv {
9+
EvSubscribe = EventSpaceBegin(TKikimrEvents::ES_FEATURE_FLAGS),
10+
EvUnsubscribe,
11+
EvChanged,
12+
EvEnd
13+
};
14+
15+
static_assert(EvEnd < EventSpaceEnd(TKikimrEvents::ES_FEATURE_FLAGS),
16+
"expect EvEnd < EventSpaceEnd(TKikimrEvents::ES_FEATURE_FLAGS)");
17+
18+
struct TEvSubscribe : public TEventLocal<TEvSubscribe, EvSubscribe> {
19+
TEvSubscribe() = default;
20+
};
21+
22+
struct TEvUnsubscribe : public TEventLocal<TEvUnsubscribe, EvUnsubscribe> {
23+
TEvUnsubscribe() = default;
24+
};
25+
26+
struct TEvChanged : public TEventLocal<TEvChanged, EvChanged> {
27+
TEvChanged() = default;
28+
};
29+
};
30+
31+
/**
32+
* Returns the service id that may be used to subscribe to feature flags
33+
* change notifications.
34+
*/
35+
TActorId MakeFeatureFlagsServiceID();
36+
37+
} // namespace NKikimr
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import os
2+
import sys
3+
from ydb.core.protos.feature_flags_pb2 import TFeatureFlags, RequireRestart
4+
5+
from jinja2 import Environment, FileSystemLoader
6+
from google.protobuf.descriptor import FieldDescriptor
7+
8+
9+
class Slot(object):
10+
def __init__(self, name, index):
11+
self.name = name
12+
self.index = index
13+
self.fields = []
14+
self.default_value = 0
15+
self.runtime_flags_mask = 0
16+
17+
18+
class Field(object):
19+
def __init__(self, name, slot, has_mask, value_mask, full_mask, default_value, is_runtime):
20+
self.name = name
21+
self.slot = slot
22+
self.has_mask = has_mask
23+
self.value_mask = value_mask
24+
self.full_mask = full_mask
25+
self.default_value = default_value
26+
self.is_runtime = is_runtime
27+
28+
29+
class EnvironmentByDir(dict):
30+
def __missing__(self, template_dir):
31+
env = Environment(loader=FileSystemLoader(template_dir))
32+
self[template_dir] = env
33+
return env
34+
35+
36+
def main():
37+
slots = []
38+
fields = []
39+
current_bits = 64
40+
for field in TFeatureFlags.DESCRIPTOR.fields:
41+
if field.type == FieldDescriptor.TYPE_BOOL:
42+
if current_bits + 2 > 64:
43+
index = len(slots)
44+
slots.append(Slot(name=f'slot{index}', index=index))
45+
current_bits = 0
46+
shift = current_bits
47+
current_bits += 2
48+
slot = slots[-1]
49+
has_mask = 1 << shift
50+
value_mask = 2 << shift
51+
full_mask = 3 << shift
52+
default_value = 0
53+
if field.default_value:
54+
default_value = 2 << shift
55+
is_runtime = not field.GetOptions().Extensions[RequireRestart]
56+
fields.append(Field(
57+
name=field.name,
58+
slot=slot,
59+
has_mask=has_mask,
60+
value_mask=value_mask,
61+
full_mask=full_mask,
62+
default_value=default_value,
63+
is_runtime=is_runtime,
64+
))
65+
slot.fields.append(fields[-1])
66+
slot.default_value |= default_value
67+
if is_runtime:
68+
slot.runtime_flags_mask |= full_mask
69+
70+
template_files = sys.argv[1::2]
71+
output_files = sys.argv[2::2]
72+
env_by_dir = EnvironmentByDir()
73+
for (template_file, output_file) in zip(template_files, output_files):
74+
(template_dir, template_name) = os.path.split(template_file)
75+
env = env_by_dir[template_dir]
76+
template = env.get_template(template_name)
77+
result = template.render(generator=__file__, slots=slots, fields=fields)
78+
with open(output_file, 'w') as f:
79+
f.write(result)
80+
print(f'Generated {output_file} from {template_name}')
81+
82+
83+
if __name__ == '__main__':
84+
main()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
PY3_PROGRAM()
2+
3+
PY_SRCS(__main__.py)
4+
5+
PEERDIR(
6+
contrib/python/MarkupSafe
7+
contrib/python/Jinja2
8+
ydb/core/protos
9+
)
10+
11+
END()
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Auto-generated by {{ generator }}, do not modify.
2+
#include <ydb/core/base/generated/runtime_feature_flags.h>
3+
#include <ydb/core/protos/feature_flags.pb.h>
4+
5+
namespace NKikimr {
6+
7+
{% for slot in slots %}
8+
std::tuple<ui64, ui64> TRuntimeFeatureFlags::BitsFromProto_{{ slot.name }}(const NKikimrConfig::TFeatureFlags& flags) {
9+
ui64 bits = 0;
10+
ui64 mask = 0;
11+
{%- for field in slot.fields %}
12+
if (flags.Has{{ field.name }}()) {
13+
bits |= flags.Get{{ field.name }}() ? {{ field.full_mask }}ULL : {{ field.has_mask }}ULL;
14+
mask |= {{ field.full_mask }}ULL;
15+
}
16+
{%- endfor %}
17+
return { bits, mask };
18+
}
19+
20+
ui64 TRuntimeFeatureFlags::FromProto_{{ slot.name }}(const NKikimrConfig::TFeatureFlags& flags, ui64 value) {
21+
auto [bits, mask] = BitsFromProto_{{ slot.name }}(flags);
22+
return (value & ~mask) | bits;
23+
}
24+
25+
void TRuntimeFeatureFlags::Update_{{ slot.name }}(ui64 bits, ui64 mask) {
26+
ui64 slot = {{ slot.name }}_.load(std::memory_order_relaxed);
27+
for (;;) {
28+
ui64 updated = (slot & ~mask) | bits;
29+
// We perform a CAS so unrelated updates by concurrent threads are not lost
30+
if ({{ slot.name }}_.compare_exchange_weak(slot, updated, std::memory_order_relaxed)) {
31+
break;
32+
}
33+
}
34+
}
35+
{% endfor %}
36+
37+
TRuntimeFeatureFlags::TRuntimeFeatureFlags()
38+
{%- for slot in slots %}
39+
{{ ',' if slot.index else ':' }} {{ slot.name }}_{ {{ slot.default_value }}ULL }
40+
{%- endfor %}
41+
{}
42+
43+
TRuntimeFeatureFlags::TRuntimeFeatureFlags(const TRuntimeFeatureFlags& flags)
44+
{%- for slot in slots %}
45+
{{ ',' if slot.index else ':' }} {{ slot.name }}_{ flags.{{ slot.name }}_.load(std::memory_order_relaxed) }
46+
{%- endfor %}
47+
{}
48+
49+
TRuntimeFeatureFlags& TRuntimeFeatureFlags::operator=(const TRuntimeFeatureFlags& flags) {
50+
{%- for slot in slots %}
51+
{{ slot.name }}_.store(
52+
flags.{{ slot.name }}_.load(std::memory_order_relaxed),
53+
std::memory_order_relaxed);
54+
{%- endfor %}
55+
return *this;
56+
}
57+
58+
TRuntimeFeatureFlags::TRuntimeFeatureFlags(const NKikimrConfig::TFeatureFlags& flags)
59+
{%- for slot in slots %}
60+
{{ ',' if slot.index else ':' }} {{ slot.name }}_{ FromProto_{{ slot.name }}(flags, {{ slot.default_value }}ULL) }
61+
{%- endfor %}
62+
{}
63+
64+
TRuntimeFeatureFlags& TRuntimeFeatureFlags::operator=(const NKikimrConfig::TFeatureFlags& flags) {
65+
CopyFrom(flags);
66+
return *this;
67+
}
68+
69+
void TRuntimeFeatureFlags::CopyFrom(const NKikimrConfig::TFeatureFlags& flags) {
70+
{%- for slot in slots %}
71+
ui64 {{ slot.name }} = FromProto_{{ slot.name }}(flags, {{ slot.default_value }}ULL);
72+
{%- endfor %}
73+
{%- for slot in slots %}
74+
{{ slot.name }}_.store({{ slot.name }}, std::memory_order_relaxed);
75+
{%- endfor %}
76+
}
77+
78+
void TRuntimeFeatureFlags::MergeFrom(const NKikimrConfig::TFeatureFlags& flags) {
79+
{%- for slot in slots %}
80+
auto [{{ slot.name }}_bits, {{ slot.name }}_mask] = BitsFromProto_{{ slot.name }}(flags);
81+
{%- endfor %}
82+
{%- for slot in slots %}
83+
Update_{{ slot.name }}({{ slot.name }}_bits, {{ slot.name }}_mask);
84+
{%- endfor %}
85+
}
86+
87+
void TRuntimeFeatureFlags::CopyRuntimeFrom(const NKikimrConfig::TFeatureFlags& flags) {
88+
{%- for slot in slots %}
89+
ui64 {{ slot.name }} = FromProto_{{ slot.name }}(flags, {{ slot.default_value }}ULL);
90+
{%- endfor %}
91+
{%- for slot in slots %}
92+
Update_{{ slot.name }}({{ slot.name }} & {{ slot.runtime_flags_mask }}ULL, {{ slot.runtime_flags_mask }}ULL);
93+
{%- endfor %}
94+
}
95+
96+
TRuntimeFeatureFlags::operator NKikimrConfig::TFeatureFlags() const {
97+
NKikimrConfig::TFeatureFlags flags;
98+
{%- for slot in slots %}
99+
ui64 {{ slot.name }} = {{ slot.name }}_.load(std::memory_order_relaxed);
100+
{%- endfor %}
101+
{%- for field in fields %}
102+
if ({{ field.slot.name }} & {{ field.has_mask }}ULL) {
103+
flags.Set{{ field.name }}(bool({{ field.slot.name }} & {{ field.value_mask }}ULL));
104+
}
105+
{%- endfor %}
106+
return flags;
107+
}
108+
109+
{% for field in fields %}
110+
bool TRuntimeFeatureFlags::Has{{ field.name }}() const {
111+
return {{ field.slot.name }}_.load(std::memory_order_relaxed) & {{ field.has_mask }}ULL;
112+
}
113+
114+
bool TRuntimeFeatureFlags::Get{{ field.name }}() const {
115+
return {{ field.slot.name }}_.load(std::memory_order_relaxed) & {{ field.value_mask }}ULL;
116+
}
117+
118+
void TRuntimeFeatureFlags::Set{{ field.name }}(bool value) {
119+
Update_{{ field.slot.name }}(value ? {{ field.full_mask }}ULL : {{ field.has_mask }}ULL, {{ field.full_mask}}ULL);
120+
}
121+
122+
void TRuntimeFeatureFlags::Clear{{ field.name }}() {
123+
Update_{{ field.slot.name }}({{ field.default_value }}ULL, {{ field.full_mask }}ULL);
124+
}
125+
{% endfor %}
126+
127+
} // namespace NKikimr

0 commit comments

Comments
 (0)