From 836410f5c9e092be4b77b26b3fc9f7abde0c89de Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Sun, 27 Nov 2011 17:22:05 +0100 Subject: Move sendfile api to file module Since sendfile could in theory be used to send to any type of file descriptor in *nix, it is a better fit to have it in file. --- lib/kernel/src/file.erl | 142 ++++++++++++++++- lib/kernel/src/gen_tcp.erl | 146 +---------------- lib/kernel/test/Makefile | 3 +- lib/kernel/test/gen_tcp_api_SUITE.erl | 286 +-------------------------------- lib/kernel/test/sendfile_SUITE.erl | 291 ++++++++++++++++++++++++++++++++++ 5 files changed, 441 insertions(+), 427 deletions(-) create mode 100644 lib/kernel/test/sendfile_SUITE.erl diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl index 706c60caaf..ef1d20b53b 100644 --- a/lib/kernel/src/file.erl +++ b/lib/kernel/src/file.erl @@ -51,6 +51,9 @@ -export([pid2name/1]). +%% Sendfile functions +-export([sendfile/2,sendfile/5]). + %%% Obsolete exported functions -export([raw_read_file_info/1, raw_write_file_info/2]). @@ -103,7 +106,10 @@ -type date_time() :: calendar:datetime(). -type posix_file_advise() :: 'normal' | 'sequential' | 'random' | 'no_reuse' | 'will_need' | 'dont_need'. - +-type sendfile_option() :: {chunk_size, non_neg_integer()} | + {headers, Hdrs :: list(iodata())} | + {trailers, Tlrs :: list(iodata())} | + sf_nodiskio | sf_mnowait | sf_sync. %%%----------------------------------------------------------------- %%% General functions @@ -1114,6 +1120,140 @@ change_time(Name, Atime, Mtime) when is_tuple(Atime), is_tuple(Mtime) -> write_file_info(Name, #file_info{atime=Atime, mtime=Mtime}). +%% +%% Send data using sendfile +%% + +-define(MAX_CHUNK_SIZE, (1 bsl 20)*20). %% 20 MB, has to fit in primary memory + +-spec sendfile(File, Sock, Offset, Bytes, Opts) -> + {'ok', non_neg_integer()} | {'error', inet:posix() | badarg | not_owner} when + File :: file:fd(), + Sock :: inet:socket(), + Offset :: non_neg_integer(), + Bytes :: non_neg_integer(), + Opts :: [sendfile_option()]. +sendfile(File, _Sock, _Offet, _Bytes, _Opts) when is_pid(File) -> + {error, badarg}; +sendfile(File, Sock, Offset, Bytes, []) -> + sendfile(File, Sock, Offset, Bytes, ?MAX_CHUNK_SIZE, [], [], + false, false, false); +sendfile(File, Sock, Offset, Bytes, Opts) -> + ChunkSize0 = proplists:get_value(chunk_size, Opts, ?MAX_CHUNK_SIZE), + ChunkSize = if ChunkSize0 > ?MAX_CHUNK_SIZE -> + ?MAX_CHUNK_SIZE; + true -> ChunkSize0 + end, + Headers = proplists:get_value(headers, Opts, []), + Trailers = proplists:get_value(trailers, Opts, []), + sendfile(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers, + lists:member(sf_nodiskio,Opts),lists:member(sf_mnowait,Opts), + lists:member(sf_sync,Opts)). + +%% sendfile/2 +-spec sendfile(File, Sock) -> + {'ok', non_neg_integer()} | {'error', inet:posix() | badarg | not_owner} + when File :: file:name(), + Sock :: inet:socket(). +sendfile(File, Sock) -> + case file:open(File, [read, raw, binary]) of + {error, Reason} -> + {error, Reason}; + {ok, Fd} -> + Res = sendfile(Fd, Sock, 0, 0, []), + file:close(Fd), + Res + end. + +%% Internal sendfile functions +sendfile(#file_descriptor{ module = Mod } = Fd, Sock, Offset, Bytes, + ChunkSize, Headers, Trailers, Nodiskio, MNowait, Sync) + when is_port(Sock) -> + case Mod:sendfile(Fd, Sock, Offset, Bytes, ChunkSize, Headers, Trailers, + Nodiskio, MNowait, Sync) of + {error, enotsup} -> + sendfile_fallback(Fd, Sock, Offset, Bytes, ChunkSize, + Headers, Trailers); + Else -> + Else + end; +sendfile(_,_,_,_,_,_,_,_,_,_) -> + {error, badarg}. + +%%% +%% Sendfile Fallback +%%% +sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, + Headers, Trailers) + when Headers == []; is_integer(Headers) -> + case sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) of + {ok, BytesSent} when is_list(Trailers), + Trailers =/= [], + is_integer(Headers) -> + sendfile_send(Sock, Trailers, BytesSent+Headers); + {ok, BytesSent} when is_list(Trailers), Trailers =/= [] -> + sendfile_send(Sock, Trailers, BytesSent); + {ok, BytesSent} when is_integer(Headers) -> + {ok, BytesSent + Headers}; + Else -> + Else + end; +sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers) -> + case sendfile_send(Sock, Headers, 0) of + {ok, BytesSent} -> + sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, BytesSent, + Trailers); + Else -> + Else + end. + + +sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) -> + {ok, CurrPos} = file:position(File, {cur, 0}), + {ok, _NewPos} = file:position(File, {bof, Offset}), + Res = sendfile_fallback_int(File, Sock, Bytes, ChunkSize, 0), + file:position(File, {bof, CurrPos}), + Res. + + +sendfile_fallback_int(File, Sock, Bytes, ChunkSize, BytesSent) + when Bytes > BytesSent; Bytes == 0 -> + Size = if Bytes == 0 -> + ChunkSize; + (Bytes - BytesSent + ChunkSize) > 0 -> + Bytes - BytesSent; + true -> + ChunkSize + end, + case file:read(File, Size) of + {ok, Data} -> + case sendfile_send(Sock, Data, BytesSent) of + {ok,NewBytesSent} -> + sendfile_fallback_int( + File, Sock, Bytes, ChunkSize, + NewBytesSent); + Error -> + Error + end; + eof -> + {ok, BytesSent}; + Error -> + Error + end; +sendfile_fallback_int(_File, _Sock, BytesSent, _ChunkSize, BytesSent) -> + {ok, BytesSent}. + +sendfile_send(Sock, Data, Old) -> + Len = iolist_size(Data), + case gen_tcp:send(Sock, Data) of + ok -> + {ok, Len+Old}; + Else -> + Else + end. + + + %%%----------------------------------------------------------------- %%% Helpers diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl index 78e3ab3697..4d6c7f5f1d 100644 --- a/lib/kernel/src/gen_tcp.erl +++ b/lib/kernel/src/gen_tcp.erl @@ -22,7 +22,7 @@ -export([connect/3, connect/4, listen/2, accept/1, accept/2, shutdown/2, close/1]). --export([send/2, recv/2, recv/3, unrecv/2, sendfile/2, sendfile/5]). +-export([send/2, recv/2, recv/3, unrecv/2]). -export([controlling_process/2]). -export([fdopen/2]). @@ -106,13 +106,8 @@ {tcp_module, module()} | option(). -type socket() :: port(). --type sendfile_option() :: {chunk_size, non_neg_integer()} | - {headers, Hdrs :: list(iodata())} | - {trailers, Tlrs :: list(iodata())} | - sf_nodiskio | sf_mnowait | sf_sync. --export_type([option/0, option_name/0, connect_option/0, listen_option/0, - sendfile_option/0]). +-export_type([option/0, option_name/0, connect_option/0, listen_option/0]). %% %% Connect a socket @@ -308,52 +303,6 @@ unrecv(S, Data) when is_port(S) -> Mod:unrecv(S, Data); Error -> Error - end. - -%% -%% Send data using sendfile -%% - --define(MAX_CHUNK_SIZE, (1 bsl 20)*20). %% 20 MB, has to fit in primary memory - --spec sendfile(File, Sock, Offset, Bytes, Opts) -> - {'ok', non_neg_integer()} | {'error', inet:posix() } | - {'error', not_owner} when - File :: file:fd(), - Sock :: socket(), - Offset :: non_neg_integer(), - Bytes :: non_neg_integer(), - Opts :: [sendfile_option()]. -sendfile(File, _Sock, _Offet, _Bytes, _Opts) when is_pid(File) -> - {error, badarg}; -sendfile(File, Sock, Offset, Bytes, []) -> - sendfile(File, Sock, Offset, Bytes, ?MAX_CHUNK_SIZE, [], [], - false, false, false); -sendfile(File, Sock, Offset, Bytes, Opts) -> - ChunkSize0 = proplists:get_value(chunk_size, Opts, ?MAX_CHUNK_SIZE), - ChunkSize = if ChunkSize0 > ?MAX_CHUNK_SIZE -> - ?MAX_CHUNK_SIZE; - true -> ChunkSize0 - end, - Headers = proplists:get_value(headers, Opts, []), - Trailers = proplists:get_value(trailers, Opts, []), - sendfile(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers, - lists:member(sf_nodiskio,Opts),lists:member(sf_mnowait,Opts), - lists:member(sf_sync,Opts)). - -%% sendfile/2 --spec sendfile(File, Sock) -> - {'ok', non_neg_integer()} | {'error', inet:posix() | badarg} - when File :: file:name(), - Sock :: socket(). -sendfile(File, Sock) -> - case file:open(File, [read, raw, binary]) of - {error, Reason} -> - {error, Reason}; - {ok, Fd} -> - Res = sendfile(Fd, Sock, 0, 0, []), - file:close(Fd), - Res end. %% @@ -407,94 +356,3 @@ mod([_|Opts], Address) -> mod([], Address) -> mod(Address). - -%% Internal sendfile functions -sendfile(#file_descriptor{ module = Mod } = Fd, Sock, Offset, Bytes, - ChunkSize, Headers, Trailers, Nodiskio, MNowait, Sync) - when is_port(Sock) -> - ok = inet:lock_socket(Sock,true), - {ok, SockFd} = prim_inet:getfd(Sock), - case Mod:sendfile(Fd, SockFd, Offset, Bytes, ChunkSize, Headers, Trailers, - Nodiskio, MNowait, Sync) of - {error, enotsup} -> - ok = inet:lock_socket(Sock,false), - sendfile_fallback(Fd, Sock, Offset, Bytes, ChunkSize, - Headers, Trailers); - Else -> - ok = inet:lock_socket(Sock,false), - Else - end; -sendfile(_,_,_,_,_,_,_,_,_,_) -> - {error, badarg}. - -%%% -%% Sendfile Fallback -%%% -sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, - Headers, Trailers) - when Headers == []; is_integer(Headers) -> - case sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) of - {ok, BytesSent} when is_list(Trailers), - Trailers =/= [], - is_integer(Headers) -> - sendfile_send(Sock, Trailers, BytesSent+Headers); - {ok, BytesSent} when is_list(Trailers), Trailers =/= [] -> - sendfile_send(Sock, Trailers, BytesSent); - {ok, BytesSent} when is_integer(Headers) -> - {ok, BytesSent + Headers}; - Else -> - Else - end; -sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, Headers, Trailers) -> - case sendfile_send(Sock, Headers, 0) of - {ok, BytesSent} -> - sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize, BytesSent, - Trailers); - Else -> - Else - end. - - -sendfile_fallback(File, Sock, Offset, Bytes, ChunkSize) -> - {ok, CurrPos} = file:position(File, {cur, 0}), - {ok, _NewPos} = file:position(File, {bof, Offset}), - Res = sendfile_fallback_int(File, Sock, Bytes, ChunkSize, 0), - file:position(File, {bof, CurrPos}), - Res. - - -sendfile_fallback_int(File, Sock, Bytes, ChunkSize, BytesSent) - when Bytes > BytesSent; Bytes == 0 -> - Size = if Bytes == 0 -> - ChunkSize; - (Bytes - BytesSent + ChunkSize) > 0 -> - Bytes - BytesSent; - true -> - ChunkSize - end, - case file:read(File, Size) of - {ok, Data} -> - case sendfile_send(Sock, Data, BytesSent) of - {ok,NewBytesSent} -> - sendfile_fallback_int( - File, Sock, Bytes, ChunkSize, - NewBytesSent); - Error -> - Error - end; - eof -> - {ok, BytesSent}; - Error -> - Error - end; -sendfile_fallback_int(_File, _Sock, BytesSent, _ChunkSize, BytesSent) -> - {ok, BytesSent}. - -sendfile_send(Sock, Data, Old) -> - Len = iolist_size(Data), - case send(Sock, Data) of - ok -> - {ok, Len+Old}; - Else -> - Else - end. diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 82bc3fc6d1..5dcaad3f5e 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -74,7 +74,8 @@ MODULES= \ wrap_log_reader_SUITE \ cleanup \ zlib_SUITE \ - loose_node + loose_node \ + sendfile_SUITE APP_FILES = \ appinc.app \ diff --git a/lib/kernel/test/gen_tcp_api_SUITE.erl b/lib/kernel/test/gen_tcp_api_SUITE.erl index d66caad2d8..a7af00c12a 100644 --- a/lib/kernel/test/gen_tcp_api_SUITE.erl +++ b/lib/kernel/test/gen_tcp_api_SUITE.erl @@ -24,7 +24,6 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/inet.hrl"). --include_lib("kernel/include/file.hrl"). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, @@ -33,42 +32,21 @@ t_connect_bad/1, t_recv_timeout/1, t_recv_eof/1, t_shutdown_write/1, t_shutdown_both/1, t_shutdown_error/1, - t_fdopen/1, t_implicit_inet6/1, - t_sendfile_small/1, t_sendfile_big/1, - t_sendfile_hdtl/1, t_sendfile_partial/1, - t_sendfile_offset/1, t_sendfile_sendafter/1, - t_sendfile_recvafter/1, t_sendfile_sendduring/1, - t_sendfile_recvduring/1]). - --export([sendfile_server/2]). + t_fdopen/1, t_implicit_inet6/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [{group, t_accept}, {group, t_connect}, {group, t_recv}, t_shutdown_write, t_shutdown_both, t_shutdown_error, - t_fdopen, t_implicit_inet6,{group,t_sendfile}]. + t_fdopen, t_implicit_inet6]. groups() -> [{t_accept, [], [t_accept_timeout]}, {t_connect, [], [t_connect_timeout, t_connect_bad]}, - {t_recv, [], [t_recv_timeout, t_recv_eof]}, - {t_sendfile, [], [{group, sendfile_all()}]}]. - -sendfile_all() -> - [ - t_sendfile_small - ,t_sendfile_big -% ,t_sendfile_hdtl - ,t_sendfile_partial - ,t_sendfile_offset - ,t_sendfile_sendafter - ,t_sendfile_recvafter - ,t_sendfile_sendduring - ,t_sendfile_recvduring - ]. -% [t_sendfile_big]. -% [t_sendfile_small]. + {t_recv, [], [t_recv_timeout, t_recv_eof]}]. + + init_per_suite(Config) -> Config. @@ -76,31 +54,12 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_group(t_sendfile, Config) -> - Priv = ?config(priv_dir, Config), - SFilename = filename:join(Priv, "sendfile_small.html"), - {ok, DS} = file:open(SFilename,[write,raw]), - file:write(DS,"yo baby yo"), - file:sync(DS), - file:close(DS), - BFilename = filename:join(Priv, "sendfile_big.html"), - {ok, DB} = file:open(BFilename,[write,raw]), - [file:write(DB,[<<0:(10*8*1024*1024)>>]) || _I <- lists:seq(1,51)], - file:sync(DB), - file:close(DB), - [{small_file, SFilename}, - {file_opts,[raw,binary]}, - {big_file, BFilename}|Config]; init_per_group(_GroupName, Config) -> Config. -end_per_group(t_sendfile, Config) -> - file:delete(proplists:get_value(big_file, Config)); end_per_group(_,_Config) -> ok. - - init_per_testcase(_Func, Config) -> Dog = test_server:timetrap(test_server:seconds(60)), [{watchdog, Dog}|Config]. @@ -278,241 +237,6 @@ implicit_inet6(S, Addr) -> ?line ok = gen_tcp:close(S2), ?line ok = gen_tcp:close(S1). -t_sendfile_small(Config) when is_list(Config) -> - Filename = proplists:get_value(small_file, Config), - - Send = fun(Sock) -> - {Size, Data} = sendfile_file_info(Filename), - {ok, Size} = gen_tcp:sendfile(Filename, Sock), - Data - end, - - ok = sendfile_send(Send). - -t_sendfile_big(Config) when is_list(Config) -> - Filename = proplists:get_value(big_file, Config), - - Send = fun(Sock) -> - {ok, #file_info{size = Size}} = - file:read_file_info(Filename), - {ok, Size} = gen_tcp:sendfile(Filename, Sock), - Size - end, - - ok = sendfile_send("localhost", Send, 0). - -t_sendfile_partial(Config) -> - Filename = proplists:get_value(small_file, Config), - FileOpts = proplists:get_value(file_opts, Config, []), - - SendSingle = fun(Sock) -> - {_Size, <>} = - sendfile_file_info(Filename), - {ok,D} = file:open(Filename,[read|FileOpts]), - {ok,5} = gen_tcp:sendfile(D,Sock,0,5,[]), - file:close(D), - Data - end, - ok = sendfile_send(SendSingle), - - {_Size, <>} = - sendfile_file_info(Filename), - {ok,D} = file:open(Filename,[read|FileOpts]), - {ok, <>} = file:read(D,5), - FSend = fun(Sock) -> - {ok,5} = gen_tcp:sendfile(D,Sock,0,5,[]), - FData - end, - - ok = sendfile_send(FSend), - - SSend = fun(Sock) -> - {ok,3} = gen_tcp:sendfile(D,Sock,5,3,[]), - SData - end, - - ok = sendfile_send(SSend), - - {ok, <>} = file:read(D,3), - - file:close(D). - -t_sendfile_offset(Config) -> - Filename = proplists:get_value(small_file, Config), - FileOpts = proplists:get_value(file_opts, Config, []), - - Send = fun(Sock) -> - {_Size, <<_:5/binary,Data:3/binary,_/binary>> = AllData} = - sendfile_file_info(Filename), - {ok,D} = file:open(Filename,[read|FileOpts]), - {ok,3} = gen_tcp:sendfile(D,Sock,5,3,[]), - {ok, AllData} = file:read(D,100), - file:close(D), - Data - end, - ok = sendfile_send(Send). - - -t_sendfile_hdtl(Config) -> - Filename = proplists:get_value(small_file, Config), - FileOpts = proplists:get_value(file_opts, Config, []), - - Send = fun(Sock, Headers, Trailers, HdtlSize) -> - {Size, Data} = sendfile_file_info(Filename), - {ok,D} = file:open(Filename,[read|FileOpts]), - AllSize = Size+HdtlSize, - {ok, AllSize} = gen_tcp:sendfile( - D, Sock,0,0, - [{headers,Headers}, - {trailers,Trailers}]), - file:close(D), - Data - end, - - SendHdTl = fun(Sock) -> - Headers = [<<"header1">>,<<0:(1024*8)>>,"header2"], - Trailers = [<<"trailer1">>,"trailer2"], - D = Send(Sock,Headers,Trailers, - iolist_size([Headers,Trailers])), - iolist_to_binary([Headers,D,Trailers]) - end, - ok = sendfile_send(SendHdTl), - - SendHd = fun(Sock) -> - Headers = [<<"header1">>,"header2"], - D = Send(Sock,Headers,undefined, - iolist_size([Headers])), - iolist_to_binary([Headers,D]) - end, - ok = sendfile_send(SendHd), - - SendTl = fun(Sock) -> - Trailers = [<<"trailer1">>,"trailer2"], - D = Send(Sock,undefined,Trailers, - iolist_size([Trailers])), - iolist_to_binary([D,Trailers]) - end, - ok = sendfile_send(SendTl). - -t_sendfile_sendafter(Config) -> - Filename = proplists:get_value(small_file, Config), - - Send = fun(Sock) -> - {Size, Data} = sendfile_file_info(Filename), - {ok, Size} = gen_tcp:sendfile(Filename, Sock), - ok = gen_tcp:send(Sock, <<2>>), - <> - end, - - ok = sendfile_send(Send). - -t_sendfile_recvafter(Config) -> - Filename = proplists:get_value(small_file, Config), - - Send = fun(Sock) -> - {Size, Data} = sendfile_file_info(Filename), - {ok, Size} = gen_tcp:sendfile(Filename, Sock), - ok = gen_tcp:send(Sock, <<1>>), - {ok,<<1>>} = gen_tcp:recv(Sock, 1), - <> - end, - - ok = sendfile_send(Send). - -t_sendfile_sendduring(Config) -> - Filename = proplists:get_value(big_file, Config), - - Send = fun(Sock) -> - {ok, #file_info{size = Size}} = - file:read_file_info(Filename), - spawn_link(fun() -> - timer:sleep(10), - ok = gen_tcp:send(Sock, <<2>>) - end), - {ok, Size} = gen_tcp:sendfile(Filename, Sock), - Size+1 - end, - - ok = sendfile_send("localhost", Send, 0). - -t_sendfile_recvduring(Config) -> - Filename = proplists:get_value(big_file, Config), - - Send = fun(Sock) -> - {ok, #file_info{size = Size}} = - file:read_file_info(Filename), - spawn_link(fun() -> - timer:sleep(10), - ok = gen_tcp:send(Sock, <<1>>), - {ok,<<1>>} = gen_tcp:recv(Sock, 1) - end), - {ok, Size} = gen_tcp:sendfile(Filename, Sock), - timer:sleep(1000), - Size+1 - end, - - ok = sendfile_send("localhost", Send, 0). - -%% TODO: consolidate tests and reduce code -sendfile_send(Send) -> - sendfile_send("localhost",Send). -sendfile_send(Host, Send) -> - sendfile_send(Host, Send, []). -sendfile_send(Host, Send, Orig) -> - spawn_link(?MODULE, sendfile_server, [self(), Orig]), - receive - {server, Port} -> - {ok, Sock} = gen_tcp:connect(Host, Port, - [binary,{packet,0}, - {active,false}]), - Data = Send(Sock), - ok = gen_tcp:close(Sock), - receive - {ok, Bin} -> - Data = Bin, - ok - end - end. - -sendfile_server(ClientPid, Orig) -> - {ok, LSock} = gen_tcp:listen(0, [binary, {packet, 0}, - {active, true}, - {reuseaddr, true}]), - {ok, Port} = inet:port(LSock), - ClientPid ! {server, Port}, - {ok, Sock} = gen_tcp:accept(LSock), - {ok, Bin} = sendfile_do_recv(Sock, Orig), - ClientPid ! {ok, Bin}, - gen_tcp:send(Sock, <<1>>). - --define(SENDFILE_TIMEOUT, 10000). -%% f(),{ok, S} = gen_tcp:connect("localhost",7890,[binary]),gen_tcp:sendfile("/ldisk/lukas/otp/sendfiletest.dat",S). -sendfile_do_recv(Sock, Bs) -> - receive - {tcp, Sock, B} -> - case binary:match(B,<<1>>) of - nomatch when is_list(Bs) -> - sendfile_do_recv(Sock, [B|Bs]); - nomatch when is_integer(Bs) -> - sendfile_do_recv(Sock, byte_size(B) + Bs); - _ when is_list(Bs) -> - {ok, iolist_to_binary(lists:reverse([B|Bs]))}; - _ when is_integer(Bs) -> - {ok, byte_size(B) + Bs} - end; - {tcp_closed, Sock} when is_list(Bs) -> - {ok, iolist_to_binary(lists:reverse(Bs))}; - {tcp_closed, Sock} when is_integer(Bs) -> - {ok, Bs} - after ?SENDFILE_TIMEOUT -> - timeout - end. - -sendfile_file_info(File) -> - {ok, #file_info{size = Size}} = file:read_file_info(File), - {ok, Data} = file:read_file(File), - {Size, Data}. - %%% Utilities diff --git a/lib/kernel/test/sendfile_SUITE.erl b/lib/kernel/test/sendfile_SUITE.erl new file mode 100644 index 0000000000..cddb783fe7 --- /dev/null +++ b/lib/kernel/test/sendfile_SUITE.erl @@ -0,0 +1,291 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(sendfile_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("kernel/include/file.hrl"). + +-compile(export_all). + +all() -> + [t_sendfile_small + ,t_sendfile_big +% ,t_sendfile_hdtl + ,t_sendfile_partial + ,t_sendfile_offset + ,t_sendfile_sendafter + ,t_sendfile_recvafter + ,t_sendfile_sendduring + ,t_sendfile_recvduring + ]. + +init_per_suite(Config) -> + Priv = ?config(priv_dir, Config), + SFilename = filename:join(Priv, "sendfile_small.html"), + {ok, DS} = file:open(SFilename,[write,raw]), + file:write(DS,"yo baby yo"), + file:sync(DS), + file:close(DS), + BFilename = filename:join(Priv, "sendfile_big.html"), + {ok, DB} = file:open(BFilename,[write,raw]), + [file:write(DB,[<<0:(10*8*1024*1024)>>]) || _I <- lists:seq(1,51)], + file:sync(DB), + file:close(DB), + [{small_file, SFilename}, + {file_opts,[raw,binary]}, + {big_file, BFilename}|Config]. + +end_per_suite(Config) -> + file:delete(proplists:get_value(big_file, Config)). + +t_sendfile_small(Config) when is_list(Config) -> + Filename = proplists:get_value(small_file, Config), + + Send = fun(Sock) -> + {Size, Data} = sendfile_file_info(Filename), + {ok, Size} = file:sendfile(Filename, Sock), + Data + end, + + ok = sendfile_send(Send). + +t_sendfile_big(Config) when is_list(Config) -> + Filename = proplists:get_value(big_file, Config), + + Send = fun(Sock) -> + {ok, #file_info{size = Size}} = + file:read_file_info(Filename), + {ok, Size} = file:sendfile(Filename, Sock), + Size + end, + + ok = sendfile_send("localhost", Send, 0). + +t_sendfile_partial(Config) -> + Filename = proplists:get_value(small_file, Config), + FileOpts = proplists:get_value(file_opts, Config, []), + + SendSingle = fun(Sock) -> + {_Size, <>} = + sendfile_file_info(Filename), + {ok,D} = file:open(Filename,[read|FileOpts]), + {ok,5} = file:sendfile(D,Sock,0,5,[]), + file:close(D), + Data + end, + ok = sendfile_send(SendSingle), + + {_Size, <>} = + sendfile_file_info(Filename), + {ok,D} = file:open(Filename,[read|FileOpts]), + {ok, <>} = file:read(D,5), + FSend = fun(Sock) -> + {ok,5} = file:sendfile(D,Sock,0,5,[]), + FData + end, + + ok = sendfile_send(FSend), + + SSend = fun(Sock) -> + {ok,3} = file:sendfile(D,Sock,5,3,[]), + SData + end, + + ok = sendfile_send(SSend), + + {ok, <>} = file:read(D,3), + + file:close(D). + +t_sendfile_offset(Config) -> + Filename = proplists:get_value(small_file, Config), + FileOpts = proplists:get_value(file_opts, Config, []), + + Send = fun(Sock) -> + {_Size, <<_:5/binary,Data:3/binary,_/binary>> = AllData} = + sendfile_file_info(Filename), + {ok,D} = file:open(Filename,[read|FileOpts]), + {ok,3} = file:sendfile(D,Sock,5,3,[]), + {ok, AllData} = file:read(D,100), + file:close(D), + Data + end, + ok = sendfile_send(Send). + + +t_sendfile_hdtl(Config) -> + Filename = proplists:get_value(small_file, Config), + FileOpts = proplists:get_value(file_opts, Config, []), + + Send = fun(Sock, Headers, Trailers, HdtlSize) -> + {Size, Data} = sendfile_file_info(Filename), + {ok,D} = file:open(Filename,[read|FileOpts]), + AllSize = Size+HdtlSize, + {ok, AllSize} = file:sendfile( + D, Sock,0,0, + [{headers,Headers}, + {trailers,Trailers}]), + file:close(D), + Data + end, + + SendHdTl = fun(Sock) -> + Headers = [<<"header1">>,<<0:(1024*8)>>,"header2"], + Trailers = [<<"trailer1">>,"trailer2"], + D = Send(Sock,Headers,Trailers, + iolist_size([Headers,Trailers])), + iolist_to_binary([Headers,D,Trailers]) + end, + ok = sendfile_send(SendHdTl), + + SendHd = fun(Sock) -> + Headers = [<<"header1">>,"header2"], + D = Send(Sock,Headers,undefined, + iolist_size([Headers])), + iolist_to_binary([Headers,D]) + end, + ok = sendfile_send(SendHd), + + SendTl = fun(Sock) -> + Trailers = [<<"trailer1">>,"trailer2"], + D = Send(Sock,undefined,Trailers, + iolist_size([Trailers])), + iolist_to_binary([D,Trailers]) + end, + ok = sendfile_send(SendTl). + +t_sendfile_sendafter(Config) -> + Filename = proplists:get_value(small_file, Config), + + Send = fun(Sock) -> + {Size, Data} = sendfile_file_info(Filename), + {ok, Size} = file:sendfile(Filename, Sock), + ok = gen_tcp:send(Sock, <<2>>), + <> + end, + + ok = sendfile_send(Send). + +t_sendfile_recvafter(Config) -> + Filename = proplists:get_value(small_file, Config), + + Send = fun(Sock) -> + {Size, Data} = sendfile_file_info(Filename), + {ok, Size} = file:sendfile(Filename, Sock), + ok = gen_tcp:send(Sock, <<1>>), + {ok,<<1>>} = gen_tcp:recv(Sock, 1), + <> + end, + + ok = sendfile_send(Send). + +t_sendfile_sendduring(Config) -> + Filename = proplists:get_value(big_file, Config), + + Send = fun(Sock) -> + {ok, #file_info{size = Size}} = + file:read_file_info(Filename), + spawn_link(fun() -> + timer:sleep(10), + ok = gen_tcp:send(Sock, <<2>>) + end), + {ok, Size} = file:sendfile(Filename, Sock), + Size+1 + end, + + ok = sendfile_send("localhost", Send, 0). + +t_sendfile_recvduring(Config) -> + Filename = proplists:get_value(big_file, Config), + + Send = fun(Sock) -> + {ok, #file_info{size = Size}} = + file:read_file_info(Filename), + spawn_link(fun() -> + timer:sleep(10), + ok = gen_tcp:send(Sock, <<1>>), + {ok,<<1>>} = gen_tcp:recv(Sock, 1) + end), + {ok, Size} = file:sendfile(Filename, Sock), + timer:sleep(1000), + Size+1 + end, + + ok = sendfile_send("localhost", Send, 0). + +%% TODO: consolidate tests and reduce code +sendfile_send(Send) -> + sendfile_send("localhost",Send). +sendfile_send(Host, Send) -> + sendfile_send(Host, Send, []). +sendfile_send(Host, Send, Orig) -> + spawn_link(?MODULE, sendfile_server, [self(), Orig]), + receive + {server, Port} -> + {ok, Sock} = gen_tcp:connect(Host, Port, + [binary,{packet,0}, + {active,false}]), + Data = Send(Sock), + ok = gen_tcp:close(Sock), + receive + {ok, Bin} -> + Data = Bin, + ok + end + end. + +sendfile_server(ClientPid, Orig) -> + {ok, LSock} = gen_tcp:listen(0, [binary, {packet, 0}, + {active, true}, + {reuseaddr, true}]), + {ok, Port} = inet:port(LSock), + ClientPid ! {server, Port}, + {ok, Sock} = gen_tcp:accept(LSock), + {ok, Bin} = sendfile_do_recv(Sock, Orig), + ClientPid ! {ok, Bin}, + gen_tcp:send(Sock, <<1>>). + +-define(SENDFILE_TIMEOUT, 10000). +%% f(),{ok, S} = gen_tcp:connect("localhost",7890,[binary]),file:sendfile("/ldisk/lukas/otp/sendfiletest.dat",S). +sendfile_do_recv(Sock, Bs) -> + receive + {tcp, Sock, B} -> + case binary:match(B,<<1>>) of + nomatch when is_list(Bs) -> + sendfile_do_recv(Sock, [B|Bs]); + nomatch when is_integer(Bs) -> + sendfile_do_recv(Sock, byte_size(B) + Bs); + _ when is_list(Bs) -> + {ok, iolist_to_binary(lists:reverse([B|Bs]))}; + _ when is_integer(Bs) -> + {ok, byte_size(B) + Bs} + end; + {tcp_closed, Sock} when is_list(Bs) -> + {ok, iolist_to_binary(lists:reverse(Bs))}; + {tcp_closed, Sock} when is_integer(Bs) -> + {ok, Bs} + after ?SENDFILE_TIMEOUT -> + timeout + end. + +sendfile_file_info(File) -> + {ok, #file_info{size = Size}} = file:read_file_info(File), + {ok, Data} = file:read_file(File), + {Size, Data}. -- cgit v1.2.3