3636 delete /1 , % remove data and delete metadata
3737 list /0 , % provide a list of metadata for all ring buffers
3838 list /1 , % provide metadata for a single ring buffer
39- read /1 % read the next value and increment read position
39+ read /1 , % read the next value and increment read position
40+ read_all /1 , % read all values starting with current read pointer
41+ ring_size /1 % determine the number of ring buffer entries
4042 ]).
4143
4244-type ring_name () :: atom ().
6163
6264% % Record stored in ets table (also used for matchspecs).
6365-record (ring_ro_metadata , {
64- name :: {meta , ring_name ()} | {meta , '_' },
65- generation = 1 :: ring_generation () | '_' ,
66- buffer :: ets :tid () | '_' ,
67- created :: erlang :timestamp () | '_' ,
68- size = 0 :: ring_size () | '_' ,
69- read_loc = 0 :: ring_loc () | 0 | '_' % 0 signifies a buffer that has never been read
66+ name :: {meta , ring_name ()} | {meta , '_' },
67+ generation = 1 :: ring_generation () | '_' ,
68+ buffer :: ets :tid () | '_' ,
69+ created :: erlang :timestamp () | '_' ,
70+ anti_swap_lock = 0 :: non_neg_integer () | '_' ,
71+ swapping_lock = 0 :: non_neg_integer () | '_' ,
72+ ring_size = 0 :: ring_size () | '_' ,
73+ read_loc = 0 :: ring_loc () | 0 | '_' % 0 signifies a buffer that has never been read
7074 }).
7175
72- -define (RING_SIZE , {# ring_ro_metadata .size , 0 }).
73- -define (RING_BUFFER , {# ring_ro_metadata .buffer , 0 }).
74- -define (LAST_READ_LOC , {# ring_ro_metadata .read_loc , 0 }).
75- -define (BUMP_GENERATION , {# ring_ro_metadata .generation , 1 }).
76+ -define (RING_SIZE , {# ring_ro_metadata .ring_size , 0 }).
77+ -define (RING_BUFFER , {# ring_ro_metadata .buffer , 0 }).
78+ -define (LAST_READ_LOC , {# ring_ro_metadata .read_loc , 0 }).
79+ -define (BUMP_GENERATION , {# ring_ro_metadata .generation , 1 }).
80+
81+ -define (READ_ANTI_SWAP , {# ring_ro_metadata .anti_swap_lock , 0 }).
82+ -define (LOCK_ANTI_SWAP , {# ring_ro_metadata .anti_swap_lock , 1 }).
83+ -define (UNLOCK_ANTI_SWAP , {# ring_ro_metadata .anti_swap_lock , - 1 }).
7684
7785-define (RESERVE_READ_LOC (__Size ), [? RING_BUFFER , {# ring_ro_metadata .read_loc , 1 , __Size , 1 }]).
86+ -define (RESERVE_READ_ALL_LOCS , [? RING_BUFFER , ? RING_SIZE , ? LAST_READ_LOC ]).
7887
7988meta_key (Ring_Name ) -> {meta , Ring_Name }.
8089make_meta (Ring_Name , Ring_Table_Id , Size ) ->
81- # ring_ro_metadata {name = meta_key (Ring_Name ), buffer = Ring_Table_Id , created = os :timestamp (), size = Size }.
90+ # ring_ro_metadata {name = meta_key (Ring_Name ), buffer = Ring_Table_Id , created = os :timestamp (), ring_size = Size }.
8291
8392% % Convert record to proplist.
8493make_ring_proplist (# ring_ro_metadata {name = {meta , Name }, generation = Gen , buffer = Buffer ,
85- created = Created , size = Size , read_loc = Read_Loc }) ->
94+ created = Created , ring_size = Size , read_loc = Read_Loc }) ->
8695 [{name , Name }, {generation , Gen }, {buffer , Buffer },
87- {created , Created }, {size , Size }, {read_loc , Read_Loc }].
96+ {created , Created }, {ring_size , Size }, {read_loc , Read_Loc }].
8897
8998% % Match specs for buffers.
9099all_rings () ->
91100 try ets :match_object (? RING_RO_TABLE , # ring_ro_metadata {name = meta_key ('_' ), _ = '_' })
92101 catch error :badarg -> []
93102 end .
103+
94104one_ring (Ring_Name ) ->
95105 try ets :match_object (? RING_RO_TABLE , # ring_ro_metadata {name = meta_key (Ring_Name ), _ = '_' })
96106 catch error :badarg -> []
@@ -99,7 +109,11 @@ one_ring(Ring_Name) ->
99109% % Use only writers to get the values so that a read lock isn't used on metadata.
100110get_ring_size (Ring_Name ) -> get_ring_metadata_field (Ring_Name , ? RING_SIZE ).
101111get_ring_last_read (Ring_Name ) -> get_ring_metadata_field (Ring_Name , ? LAST_READ_LOC ).
112+ get_ring_read_all (Ring_Name ) -> get_ring_metadata_field (Ring_Name , ? RESERVE_READ_ALL_LOCS ).
102113
114+ get_ring_size_lock (Ring_Name ) -> get_ring_metadata_field (Ring_Name , [? RING_SIZE , ? LOCK_ANTI_SWAP ]).
115+ unlock_anti_swap (Ring_Name ) -> get_ring_metadata_field (Ring_Name , ? UNLOCK_ANTI_SWAP ).
116+
103117% % Reserve the next read location for the calling process to read.
104118% % Since the pointer starts at 0, the reserved location is after increment
105119% % and wrapping is applied. The LAST_READ_LOC will return this same value
@@ -113,10 +127,12 @@ get_ring_last_read (Ring_Name) -> get_ring_metadata_field(Ring_Name, ?LAST_READ_
113127% % will be highly contended itself.
114128get_ring_next_read (Ring_Name ) ->
115129 % % Unfortunately, must fetch size before increment so we can properly wrap around the read pointer.
116- case get_ring_size (Ring_Name ) of
117- 0 -> {undefined , 0 , 0 };
118- Size -> [Ring_Buffer , Read_Loc ] = get_ring_metadata_field (Ring_Name , ? RESERVE_READ_LOC (Size )),
119- {Ring_Buffer , Size , Read_Loc }
130+ % % This creates a race that could fail when a ring buffer is replaced, so we lock against swaps.
131+ try get_ring_size_lock (Ring_Name ) of
132+ [0 , _ ] -> {undefined , 0 , 0 };
133+ [Size , _ ] -> [Ring_Buffer , Read_Loc ] = get_ring_metadata_field (Ring_Name , ? RESERVE_READ_LOC (Size )),
134+ {Ring_Buffer , Size , Read_Loc }
135+ after _ = unlock_anti_swap (Ring_Name )
120136 end .
121137
122138% % Uses update_counter to maintain the write_concurrency lock.
@@ -152,68 +168,70 @@ make_ring_data(Name, Loc, Data) -> #ring_ro_data{key=ring_key(Name, Loc), data=D
152168% %% External API
153169% %%------------------------------------------------------------------------------
154170
171+ -define (ENSURE_METADATA ,
172+ ets :info (? RING_RO_TABLE , named_table ) =/= undefined
173+ orelse epocxy_ets_fsm :create_ets_table (? RING_RO_TABLE , write_only )).
174+
155175-spec list () -> [proplists :proplist ()].
156176-spec list (ring_name ()) -> proplists :proplist ().
157177
158- -spec create (ring_name (), [ring_data ()]) -> boolean (). % with values
159- -spec create (ring_name ()) -> boolean (). % empty ring
160- -spec clear (ring_name ()) -> boolean (). % eliminate ring data only
161- -spec delete (ring_name ()) -> boolean (). % eliminate ring and metadata
162- -spec read (ring_name ()) -> {ok , ring_data ()} | {error , ring_error ()}.
178+ -spec create (ring_name (), [ring_data ()]) -> boolean (). % with values
179+ -spec create (ring_name ()) -> boolean (). % empty ring
180+ -spec clear (ring_name ()) -> boolean (). % eliminate ring data only
181+ -spec delete (ring_name ()) -> boolean (). % eliminate ring and metadata
182+ -spec read (ring_name ()) -> {ok , ring_data () } | {error , ring_error ()}.
183+ -spec read_all (ring_name ()) -> {ok , [ring_data ()] } | {error , ring_error ()}.
184+ -spec ring_size (ring_name ()) -> {ok , ring_size () } | {error , ring_error ()}.
163185
164186% % @doc Get a set of proplists for all ring buffers in the metadata ets table.
165- list () -> [make_ring_proplist (Ring_Metadata ) || Ring_Metadata <- all_rings ()].
187+ list () ->
188+ ? ENSURE_METADATA ,
189+ [make_ring_proplist (Ring_Metadata ) || Ring_Metadata <- all_rings ()].
166190
167191% % @doc Get a single proplist for a given ring buffer in the metadata ets table.
168192list (Ring_Name ) when is_atom (Ring_Name ) ->
193+ ? ENSURE_METADATA ,
169194 case one_ring (Ring_Name ) of
170195 [] -> [];
171196 [Ring_Metadata ] -> make_ring_proplist (Ring_Metadata )
172197 end .
173198
174199% % @doc Initialize an empty ring buffer.
175200create (Ring_Name ) when is_atom (Ring_Name ) ->
176-
177- % % Create global read_only ring ets meta-table when first ring is created...
178- ets :info (? RING_RO_TABLE , named_table ) =/= undefined
179- orelse epocxy_ets_fsm :create_ets_table (? RING_RO_TABLE , write_only ),
180-
181- % % Then insert the metadata record.
201+ ? ENSURE_METADATA ,
182202 ets :insert_new (? RING_RO_TABLE , make_meta (Ring_Name , undefined , 0 )).
183203
184-
185204% % @doc
186205% % Initialize a ring buffer with a set of values. The metadata is initialized
187206% % with the buffer size set to the number of values and the current read pointer
188207% % set to an initial value prior to the first element of the ring.
189208% % @end
190209create (Ring_Name , Ring_Values )
191210 when is_atom (Ring_Name ), is_list (Ring_Values ) ->
192-
193- % % Create global read_only ring ets meta-table when first ring is created...
194- ets :info (? RING_RO_TABLE , named_table ) =/= undefined
195- orelse epocxy_ets_fsm :create_ets_table (? RING_RO_TABLE , write_only ),
196-
197211 % % Allocate a new unnamed ets table to hold the ring values...
212+ ? ENSURE_METADATA ,
198213 Ring_Buffer = epocxy_ets_fsm :create_ets_table (read_only ),
199214 case create_ring (Ring_Name , Ring_Buffer , Ring_Values ) of
200215 true -> true ;
201216
202217 % % Eliminating created tables if there are errors inserting any of the values:
203218 % % 1) Ring name already exists
204- % % 2) Duplicate keys already exist in the ring_buffer
219+ % % 2) Duplicate keys in the list of values (or someone else beat us inserting)
205220 false -> ets :delete (? RING_RO_TABLE , meta_key (Ring_Name )),
206221 epocxy_ets_fsm :delete_ets_table (Ring_Buffer )
207222 end .
208223
209224% % @doc
210225% % Create a new ring buffer table, then replace the metadata definition of the
211226% % ring buffer, and finally delete the original ring buffer data table.
227+ % % A synchronous block of all readers should be applied during the call to
228+ % % replace the ring buffer.
212229% % @end
213230replace (Ring_Name , Ring_Values )
214231 when is_atom (Ring_Name ), is_list (Ring_Values ) ->
215232
216233 % % Allocate a new unnamed ets table to hold the ring values...
234+ ? ENSURE_METADATA ,
217235 Ring_Buffer = epocxy_ets_fsm :create_ets_table (read_only ),
218236 replace_ring (Ring_Name , Ring_Buffer , Ring_Values ).
219237
@@ -223,74 +241,110 @@ replace(Ring_Name, Ring_Values)
223241% % lock penalty, but it is used infrequently.
224242% % @end
225243clear (Ring_Name ) when is_atom (Ring_Name ) ->
226- case get_ring_metadata (Ring_Name ) of
227- missing -> false ;
228-
229- # ring_ro_metadata {buffer = Ring_Buffer } ->
230- % % Clear the pointers in the metadata first, for immediate effect...
231- try reset_metadata (Ring_Name , undefined , [])
232- after epocxy_ets_fsm :delete_ets_table (Ring_Buffer )
233- end
234- end .
244+ clear (Ring_Name , get_ring_metadata (Ring_Name )).
245+
246+ clear (_Ring_Name , false ) -> false ;
247+ clear (_Ring_Name , missing ) -> false ;
248+ clear ( Ring_Name , # ring_ro_metadata {anti_swap_lock = 0 , buffer = Ring_Buffer }) ->
249+ % % Clear the pointers in the metadata first, for mmediate effect...
250+ try reset_metadata (Ring_Name , undefined , [])
251+ after epocxy_ets_fsm :delete_ets_table (Ring_Buffer )
252+ end ;
253+ % % Anti swap is locked, try again after swap action finishes.
254+ clear ( Ring_Name , # ring_ro_metadata {}) ->
255+ erlang :yield (),
256+ clear (Ring_Name ).
235257
236258% % @doc
237259% % Delete the ring metadata, then delete the ring data buffer. Thie function
238260% % does a read on the ring metadata table so it will incur a slow lock
239261% % penalty, but it is used infrequently.
240262% % @end
241263delete (Ring_Name ) when is_atom (Ring_Name ) ->
242- case get_ring_metadata (Ring_Name ) of
243- missing -> false ;
244- # ring_ro_metadata {buffer = Ring_Buffer } ->
245- try true = ets :delete (? RING_RO_TABLE , meta_key (Ring_Name ))
246- after Ring_Buffer =/= undefined
247- andalso epocxy_ets_fsm :delete_ets_table (Ring_Buffer )
248- end
249- end .
264+ delete (Ring_Name , get_ring_metadata (Ring_Name )).
265+
266+ delete (_Ring_Name , false ) -> false ;
267+ delete (_Ring_Name , missing ) -> false ;
268+ delete ( Ring_Name , # ring_ro_metadata {anti_swap_lock = 0 , buffer = Ring_Buffer }) ->
269+ try true = ets :delete (? RING_RO_TABLE , meta_key (Ring_Name ))
270+ after Ring_Buffer =/= undefined
271+ andalso epocxy_ets_fsm :delete_ets_table (Ring_Buffer )
272+ end ;
273+ % % Anti swap is locked, try again after swap action finishes.
274+ delete ( Ring_Name , # ring_ro_metadata {}) ->
275+ erlang :yield (),
276+ delete (Ring_Name ).
250277
251278% % @doc
252279% % Reserve the next read location, then read the data from the ring
253280% % buffer ets table. The reservation is highly contended on a metadata
254281% % record lock, but the actual read is much more concurrent.
255282% % @end
256283read (Ring_Name ) when is_atom (Ring_Name ) ->
257- case get_ring_next_read (Ring_Name ) of
258- {undefined , 0 , 0 } ->
259- {error , {buffer_is_empty , Ring_Name }};
260- {Ring_Buffer , _Size , Location } when is_integer (Location ), Location > 0 ->
261- read_value (Ring_Name , Ring_Buffer , Location )
262- end .
284+ read (Ring_Name , get_ring_next_read (Ring_Name )).
285+
286+ read (Ring_Name , false ) -> {error , {buffer_is_empty , Ring_Name }};
287+ read (Ring_Name , {undefined , 0 , 0 } ) -> {error , {buffer_is_empty , Ring_Name }};
288+ read (Ring_Name , {Ring_Buffer , _Size , Location })
289+ when is_integer (Location ), Location > 0 ->
290+ read_value (Ring_Name , Ring_Buffer , Location ).
291+
292+ % % @doc
293+ % % Discover the ring size and current read location, then read all
294+ % % data from the buffer ets table. The data is read starting with
295+ % % the current read location forward, wrapping and reading all data.
296+ % % @end
297+ read_all (Ring_Name ) when is_atom (Ring_Name ) ->
298+ read_all (Ring_Name , get_ring_read_all (Ring_Name )).
299+
300+ read_all (Ring_Name , false ) -> {error , {buffer_missing , Ring_Name }};
301+ read_all (Ring_Name , {undefined , 0 , 0 }) -> {error , {buffer_is_empty , Ring_Name }};
302+ read_all (Ring_Name , {Ring_Buffer , Size , Location })
303+ when is_integer (Size ), Size > 0 ,
304+ is_integer (Location ), Location > 0 ->
305+ read_all_values (Ring_Name , Ring_Buffer , Location , Size ).
306+
307+ % % @doc Return the ring size of a buffer.
308+ ring_size (Ring_Name ) when is_atom (Ring_Name ) ->
309+ ? ENSURE_METADATA ,
310+ get_ring_size (Ring_Name ).
263311
264312
265313% %%------------------------------------------------------------------------------
266314% %% Internal functions
267315% %%------------------------------------------------------------------------------
268316
269317create_ring (Ring_Name , Ring_Buffer , Ring_Values ) ->
318+ insert_values (Ring_Name , Ring_Buffer , Ring_Values , 1 ),
270319 case ets :insert_new (? RING_RO_TABLE , make_meta (Ring_Name , Ring_Buffer , length (Ring_Values ))) of
271- false -> false ;
272- true -> insert_values (Ring_Name , Ring_Buffer , Ring_Values , 1 )
320+ false -> epocxy_ets_fsm :delete_ets_table (Ring_Buffer ),
321+ false ;
322+ true -> true
273323 end .
274324
275325replace_ring (Ring_Name , New_Ring_Buffer , Ring_Values ) ->
276- case get_ring_metadata (Ring_Name ) of
277- missing ->
278- epocxy_ets_fsm :delete_ets_table (New_Ring_Buffer ),
279- false ;
280- # ring_ro_metadata {buffer = Old_Ring_Buffer } ->
281- true = insert_values (Ring_Name , New_Ring_Buffer , Ring_Values , 1 ),
282- try reset_metadata (Ring_Name , New_Ring_Buffer , Ring_Values )
283- after Old_Ring_Buffer =/= undefined
284- andalso epocxy_ets_fsm :delete_ets_table (Old_Ring_Buffer )
285- end
286- end .
326+ true = insert_values (Ring_Name , New_Ring_Buffer , Ring_Values , 1 ),
327+ replace_ring (Ring_Name , New_Ring_Buffer , Ring_Values , get_ring_metadata (Ring_Name )).
328+
329+ replace_ring (_Ring_Name , New_Ring_Buffer , _Ring_Values , missing ) ->
330+ epocxy_ets_fsm :delete_ets_table (New_Ring_Buffer ),
331+ false ;
332+ replace_ring ( Ring_Name , New_Ring_Buffer , Ring_Values ,
333+ # ring_ro_metadata {anti_swap_lock = 0 , buffer = Old_Ring_Buffer }) ->
334+ try reset_metadata (Ring_Name , New_Ring_Buffer , Ring_Values )
335+ after Old_Ring_Buffer =:= undefined
336+ orelse epocxy_ets_fsm :delete_ets_table (Old_Ring_Buffer )
337+ end ;
338+ % % Anti-swap lock is set, wait for it to clear.
339+ replace_ring ( Ring_Name , New_Ring_Buffer , Ring_Values , # ring_ro_metadata {}) ->
340+ replace_ring (Ring_Name , New_Ring_Buffer , Ring_Values , get_ring_metadata (Ring_Name )).
287341
288342reset_metadata (Ring_Name , New_Ring_Buffer , Ring_Values ) ->
289343 Meta_Key = meta_key (Ring_Name ),
290- Reset_Values = [{# ring_ro_metadata .buffer , New_Ring_Buffer },
291- {# ring_ro_metadata .created , os :timestamp ()},
292- {# ring_ro_metadata .size , length (Ring_Values )},
293- {# ring_ro_metadata .read_loc , 0 }],
344+ Reset_Values = [{# ring_ro_metadata .buffer , New_Ring_Buffer },
345+ {# ring_ro_metadata .created , os :timestamp ()},
346+ {# ring_ro_metadata .ring_size , length (Ring_Values )},
347+ {# ring_ro_metadata .read_loc , 0 }],
294348 ets :update_element (? RING_RO_TABLE , Meta_Key , Reset_Values ),
295349
296350 % % Then increment the generation (in case someone else updated), and remove ring data.
@@ -314,3 +368,8 @@ read_value(Ring_Name, Ring_Buffer, Location) ->
314368 try ets :lookup_element (Ring_Buffer , Key , # ring_ro_data .data )
315369 catch error :badarg -> {missing_ring_data , Key }
316370 end .
371+
372+ read_all_values (Ring_Name , Ring_Buffer , Location , Size ) ->
373+ [read_value (Ring_Name , Ring_Buffer , Pos )
374+ || Pos <- lists :seq (Location , Size - Location )
375+ ++ lists :seq (1 , Location )].
0 commit comments