@@ -74,7 +74,9 @@ void _filterx_type_init_methods(FilterXType *type);
74
74
__VA_ARGS__ \
75
75
}
76
76
77
- #define FILTERX_OBJECT_MAGIC_BIAS G_MAXINT32
77
+ #define FILTERX_OBJECT_REFCOUNT_FROZEN (G_MAXINT32)
78
+ #define FILTERX_OBJECT_REFCOUNT_STACK (G_MAXINT32-1)
79
+ #define FILTERX_OBJECT_REFCOUNT_OFLOW_MARK (FILTERX_OBJECT_REFCOUNT_STACK-1024)
78
80
79
81
80
82
FILTERX_DECLARE_TYPE (object );
@@ -110,7 +112,7 @@ void filterx_object_free_method(FilterXObject *self);
110
112
static inline gboolean
111
113
filterx_object_is_frozen (FilterXObject * self )
112
114
{
113
- return g_atomic_counter_get (& self -> ref_cnt ) == FILTERX_OBJECT_MAGIC_BIAS ;
115
+ return g_atomic_counter_get (& self -> ref_cnt ) == FILTERX_OBJECT_REFCOUNT_FROZEN ;
114
116
}
115
117
116
118
static inline FilterXObject *
@@ -119,12 +121,53 @@ filterx_object_ref(FilterXObject *self)
119
121
if (!self )
120
122
return NULL ;
121
123
122
- if (filterx_object_is_frozen (self ))
123
- return self ;
124
+ gint r = g_atomic_counter_get (& self -> ref_cnt );
125
+ if (r < FILTERX_OBJECT_REFCOUNT_OFLOW_MARK && r > 0 )
126
+ {
127
+ /* NOTE: getting into this path is racy, as two threads might be
128
+ * checking the overflow mark in parallel and then decide we need to
129
+ * run this (normal) path. In this case, the race could cause ref_cnt
130
+ * to reach FILTERX_OBJECT_REFCOUNT_OFLOW_MARK, without triggering the
131
+ * overflow assert below.
132
+ *
133
+ * To mitigate this, FILTERX_OBJECT_REFCOUNT_OFLOW_MARK is set to 1024
134
+ * less than the first value that we handle specially. This means
135
+ * that even if the race is lost, we would need 1024 competing CPUs
136
+ * concurrently losing the race and incrementing ref_cnt here. And
137
+ * even in this case the only issue is that we don't detect an actual
138
+ * overflow at runtime that should never occur in the first place.
139
+ *
140
+ * This is _really_ unlikely, and we will detect ref_cnt overflows in
141
+ * non-doom scenarios first, so we can address the actual issue (which
142
+ * might be a reference counting bug somewhere).
143
+ *
144
+ * If less than 1024 CPUs lose the race, then the refcount would end
145
+ * up in the range between FILTERX_OBJECT_REFCOUNT_OFLOW_MARK and
146
+ * FILTERX_OBJECT_REFCOUNT_STACK, causing the assertion at the end of
147
+ * this function to trigger an abort.
148
+ *
149
+ * The non-racy solution would be to use a
150
+ * g_atomic_int_exchange_and_add() call and checking the old_value
151
+ * against FILTERX_OBJECT_REFCOUNT_OFLOW_MARK another time, but that's
152
+ * an extra conditional in a hot-path.
153
+ */
154
+
155
+ g_atomic_counter_inc (& self -> ref_cnt );
156
+ return self ;
157
+ }
124
158
125
- g_atomic_counter_inc (& self -> ref_cnt );
159
+ if (r == FILTERX_OBJECT_REFCOUNT_FROZEN )
160
+ return self ;
126
161
127
- return self ;
162
+ if (r == FILTERX_OBJECT_REFCOUNT_STACK )
163
+ {
164
+ /* we can't use filterx_object_clone() directly, as that's an inline
165
+ * function declared further below. Also, filterx_object_clone() does
166
+ * not clone inmutable objects. We only support allocating inmutable
167
+ * objects on the stack */
168
+ return self -> type -> clone (self );
169
+ }
170
+ g_assert_not_reached ();
128
171
}
129
172
130
173
static inline void
@@ -133,10 +176,30 @@ filterx_object_unref(FilterXObject *self)
133
176
if (!self )
134
177
return ;
135
178
136
- if (filterx_object_is_frozen (self ))
179
+ gint r = g_atomic_counter_get (& self -> ref_cnt );
180
+ if (r == FILTERX_OBJECT_REFCOUNT_FROZEN )
137
181
return ;
182
+ if (r == FILTERX_OBJECT_REFCOUNT_STACK )
183
+ {
184
+ /* NOTE: Normally, stack based allocations are only used by a single
185
+ * thread. Furthermore, code where we use this object will only have
186
+ * a borrowed reference, e.g. it can't cross thread boundaries. With
187
+ * that said, even though the condition of this if() statement is
188
+ * racy, it's not actually racing, as we only have a single relevant
189
+ * thread.
190
+ *
191
+ * In the rare case where we do want to pass a stack based allocation
192
+ * to another thread, we would need to pass a new reference to it, and
193
+ * filterx_object_ref() clones a new object in this case, which again
194
+ * means, that we won't have actual race here.
195
+ */
196
+
197
+ g_atomic_counter_set (& self -> ref_cnt , 0 );
198
+ return ;
199
+ }
200
+ if (r <= 0 )
201
+ g_assert_not_reached ();
138
202
139
- g_assert (g_atomic_counter_get (& self -> ref_cnt ) > 0 );
140
203
if (g_atomic_counter_dec_and_test (& self -> ref_cnt ))
141
204
{
142
205
self -> type -> free_fn (self );
@@ -354,4 +417,14 @@ filterx_object_set_modified_in_place(FilterXObject *self, gboolean modified)
354
417
self -> modified_in_place = modified ;
355
418
}
356
419
420
+ #define FILTERX_OBJECT_STACK_INIT (_type ) \
421
+ { \
422
+ .ref_cnt = { .counter = FILTERX_OBJECT_REFCOUNT_STACK }, \
423
+ .fx_ref_cnt = { .counter = 0 }, \
424
+ .modified_in_place = FALSE, \
425
+ .readonly = TRUE, \
426
+ .weak_referenced = FALSE, \
427
+ .type = &FILTERX_TYPE_NAME(_type) \
428
+ }
429
+
357
430
#endif
0 commit comments