Lost data when sending to a linked in driver

Raimo Niskanen <>
Fri May 31 10:43:46 CEST 2002


Sounds like a bug to me, I will look into it.

As a comfort, i guess that SMALL_WRITE_VEC will probably never become
less than 16. It is now set to match the least allowed value according
to Posix.1g of IOV_MAX in sys/uio.h.

/ Raimo Niskanen, Erlang/OTP, Ericsson AB



Shawn Pearce wrote:
> 
> I'm using R8B-1 and a custom linked in driver that I'm currently developing.
> The driver uses the outputv hook to receive binary data sent by the Erlang
> node.  When I'm sending data to the driver, I use erlang:port_call to set
> the driver into some state, at which point it receives data through the
> outputv hook (using Port ! {self(), {command, SomeList}}).
> 
> SomeList is a proper Erlang list of 32 binaries, each 1.5 MB in size.
> 
> Erts decides to send 1 element in the ErlIOVec which is passed to my
> driver's outputv hook.  This element is the customary 0th "empty"
> element for the driver to hook in any header data prior to transmission.
> None of the binaries I sent to the driver from Erlang were presented to
> the driver.
> 
> However, if I send a list of only 15 binaries, each 1.5 MB in size
> they all appear in the ErlIOVec.  The driver gets 16 items in the
> ErlIOVec, with the 15 binaries supplied starting at index 1, as
> they should.
> 
> Unfortunately, there seems to be no way for Erlang or the driver to
> know that Erts dropped the list of binaries on the floor when it
> exceeds 15 elements.  If these binaries were smaller, I'd expect
> that Erts would compact them into a single binary, but they aren't
> (and I can't shrink them, its my data's natural chunk size).
> I also don't really want Erts copying 32 MB of data into a compacted
> binary here.  Zero-copy needs to be the name of the game. :-)
> 
> So I've come up with this twisted chunk of code for the port driver
> to send to my linked in driver:
> 
>         % Taken from erts/emulator/beam/io.c: SMALL_WRITE_VEC = 16
>         % This is how many binaries we can send down.  io.c adds one binary on top
>         % of this in case the driver wants to use it to define a packet header.
>         % This header slot is unused, giving us 15 buffers.
> -define(MAX_IOVEC, 15).
> 
> post_frames({Port}, FrameList) ->
>     post_frames(Port, FrameList, [], 0).
> 
> post_frames(_, [], [], 0) ->
>         ok;
> post_frames(Port, [], Bundle, Cnt) ->
>         Port ! {self(), {command, lists:reverse(Bundle)}},
>         receive
>                 {Port, frames_queued, Count} ->
>                         ok
>         end;
> post_frames(Port, FrameList, Bundle, Cnt) when Cnt == ?MAX_IOVEC ->
>         Port ! {self(), {command, lists:reverse(Bundle)}},
>         receive
>                 {Port, frames_queued, Count} ->
>                         post_frames(Port, FrameList, [], 0)
>         end;
> post_frames(Port, [Frame | List], Bundle, Cnt) ->
>         post_frames(Port, List, [Frame | Bundle], Cnt + 1).
> 
> This code however, screams out to me as having a few issues.  The first
> is that MAX_IOVEC is set deep down inside of the emulator.  There is
> no way to get this value from Erlang at runtime, or from the driver
> at compile time or runtime.  If SMALL_WRITE_VEC ever changed in the
> eumulator from 16 to another value, this code could break.
> 
> The second issue is that when creating the list of frames which can
> be sent to the driver, I need to compose that list, then reverse it.
> Would it be better if I wrote the post_frames/4 function more like:
> 
> post_frames(Port, [F1,F2,F3,F4,F5,F6,F7,F8 | T]) ->
>         Port ! {self(), {command, [F1,F2,F3,F4,F5,F6,F7,F8]}},
>         receive
>                 {Port, frames_queued, Count} ->
>                         post_frames(Port, T)
>         end;
> post_frames(Port, [F1,F2,F3,F4 | T]) ->
>         Port ! {self(), {command, [F1,F2,F3,F4]}},
>         receive
>                 {Port, frames_queued, Count} ->
>                         post_frames(Port, T)
>         end;
> post_frames(Port, [F1,F2 | T]) ->
>         Port ! {self(), {command, [F1,F2]}},
>         receive
>                 {Port, frames_queued, Count} ->
>                         post_frames(Port, T)
>         end;
> post_frames(Port, [F1 | T]) ->
>         Port ! {self(), {command, [F1,F2]}},
>         receive
>                 {Port, frames_queued, Count} ->
>                         post_frames(Port, T)
>         end;
> post_frames(Port, []) ->
>         ok.
> 
> as this version now does not require creating two lists, and reversing one of
> them?  Unfortunately, it still suffers from the problem that if the value of
> SMALL_WRITE_VEC ever changed from 16 to smaller than 8 I could run the risk
> of losing all 8 binaries when they are matched in the first clause.
> 
> I would really like to be able to send a group of binaries to the driver
> at once, as the driver is using locking a pthread mutexes directly to
> interface to some "legacy" pthread code, delivering the group of
> binaries to that "legacy" pthread (after incremeting ErlDrvBinary.refc.
> 
> The cost of sending one binary is about the same as sending 8 or 12 binaries
> at a time, due to the cost of locking and unlocking a pthread mutex, so I'd
> like to 'batch' them as much as possible, especially since the port driver
> will be receiving batches of binaries from the rest of the Erlang system.
> 
> At any rate, no matter how I implement my code, it looks like there might
> be a bug in io.c when the number of binaries exceeds 15 and their size is
> too large to compact them.
> 
> --
> Shawn.
> 
> Why do I like Erlang?  Because ``it'll drop your data for you, without
> asking you.''  (See above email.  :-)
> 
> Why do I like Perl?  Because ``in accordance with Unix tradition Perl
> gives you enough rope to hang yourself with.''
> 
> Why do I dislike Java? Because ``the class ROPE that should contain the
> method HANG to do the hanging doesn't exist because there is too much
> 'security' built into the base language.''



More information about the erlang-questions mailing list