%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(prim_buffer).
-export([on_load/0]).
%% This is a mutable binary buffer that helps break out buffering logic from
%% NIFs/drivers, which is often the only thing that prevents the C code from
%% being reduced to bare system call wrappers.
%%
%% All operations in this file are thread-unsafe and risk crashing the emulator
%% if you're not careful.
-export([new/0, size/1, wipe/1, read/2, read_iovec/2, write/2, skip/2]).
-export([find_byte_index/2]).
-export([try_lock/1, unlock/1]).
-type prim_buffer() :: term().
%% Controls when to copy rather than extract sub-binaries from the buffer,
%% reducing the risk of small reads keeping a large binary alive.
-define(COPYING_READ_LIMIT, 512).
%% Reads that fit into heap binaries are always copied since the cost of
%% peeking binaries that short is largely equivalent to copying.
-define(ERL_ONHEAP_BIN_LIMIT, 64).
on_load() ->
case erlang:load_nif(atom_to_list(?MODULE), 0) of
ok -> ok
end.
-spec new() -> prim_buffer().
new() ->
erlang:nif_error(undef).
-spec size(Buffer :: prim_buffer()) -> non_neg_integer().
size(_Buffer) ->
erlang:nif_error(undef).
%% Reads data as a binary from the front of the buffer. This will almost always
%% result in copying so it should be avoided unless you absolutely must have a
%% binary.
-spec read(Buffer :: prim_buffer(), Size :: non_neg_integer()) -> binary().
read(Buffer, Size) when Size =< ?ERL_ONHEAP_BIN_LIMIT ->
copying_read(Buffer, Size);
read(Buffer, Size) when Size > ?ERL_ONHEAP_BIN_LIMIT ->
iolist_to_binary(read_iovec(Buffer, Size)).
%% Reads data as an erlang:iovec() binary from the front of the buffer,
%% avoiding copying if reasonable.
-spec read_iovec(Buffer, Size) -> IOVec when
Buffer :: prim_buffer(),
Size :: non_neg_integer(),
IOVec :: erlang:iovec().
read_iovec(Buffer, Size) when Size =< ?ERL_ONHEAP_BIN_LIMIT ->
[copying_read(Buffer, Size)];
read_iovec(Buffer, Size) when Size > ?ERL_ONHEAP_BIN_LIMIT ->
Head = peek_head(Buffer),
HeadSize = byte_size(Head),
if
(HeadSize - Size) > ?COPYING_READ_LIMIT, Size =< ?COPYING_READ_LIMIT ->
[copying_read(Buffer, Size)];
HeadSize > Size ->
skip(Buffer, Size),
{First, _Rest} = split_binary(Head, Size),
[First];
HeadSize < Size ->
skip(Buffer, HeadSize),
[Head | read_iovec(Buffer, Size - HeadSize)];
HeadSize =:= Size ->
skip(Buffer, Size),
[Head]
end.
%% Writes an erlang:iovec() to the back of the buffer.
-spec write(Buffer :: prim_buffer(), IOVec :: erlang:iovec()) -> ok.
write(_Buffer, _IOVec) ->
erlang:nif_error(undef).
%% Removes data from the front of the buffer without reading it.
-spec skip(Buffer :: prim_buffer(), Size :: non_neg_integer()) -> ok.
skip(_Buffer, _Size) ->
erlang:nif_error(undef).
-spec wipe(Buffer :: prim_buffer()) -> ok.
wipe(Buffer) ->
skip(Buffer, prim_buffer:size(Buffer)).
%% Finds the start-index of the first occurence of Needle, for implementing
%% read_line and similar.
-spec find_byte_index(Buffer, Needle) -> Result when
Buffer :: prim_buffer(),
Needle :: non_neg_integer(),
Result :: {ok, non_neg_integer()} |
not_found.
find_byte_index(_Buffer, _Needle) ->
erlang:nif_error(undef).
%% Attempts to take a unique lock on the buffer. Failure handling is left to
%% the user.
-spec try_lock(Buffer :: prim_buffer()) -> acquired | busy.
try_lock(_Buffer) ->
erlang:nif_error(undef).
-spec unlock(Buffer :: prim_buffer()) -> ok.
unlock(_Buffer) ->
erlang:nif_error(undef).
%% Unexported helper functions:
%% Reads data from the front of the buffer, returning a copy of the data to
%% avoid holding references.
-spec copying_read(Buffer :: prim_buffer(), Size :: non_neg_integer()) -> binary().
copying_read(_Buffer, _Size) ->
erlang:nif_error(undef).
%% Returns the binary at the front of the buffer without modifying the buffer.
-spec peek_head(Buffer :: prim_buffer()) -> binary().
peek_head(_Buffer) ->
erlang:nif_error(undef).