Feature #5138 » try_nonblock.diff
ext/openssl/lib/openssl/buffering.rb | ||
---|---|---|
ret
|
||
end
|
||
def try_read_nonblock(maxlen, buf=nil)
|
||
if maxlen == 0
|
||
if buf
|
||
buf.clear
|
||
return buf
|
||
else
|
||
return ""
|
||
end
|
||
end
|
||
if @rbuffer.empty?
|
||
return try_sysread_nonblock(maxlen, buf)
|
||
end
|
||
ret = consume_rbuff(maxlen)
|
||
if buf
|
||
buf.replace(ret)
|
||
ret = buf
|
||
end
|
||
raise EOFError if ret.empty?
|
||
ret
|
||
end
|
||
##
|
||
# Reads the next "line+ from the stream. Lines are separated by +eol+. If
|
||
# +limit+ is provided the result will not be longer than the given number of
|
||
... | ... | |
syswrite_nonblock(s)
|
||
end
|
||
def try_write_nonblock(s)
|
||
flush
|
||
try_syswrite_nonblock(s)
|
||
end
|
||
##
|
||
# Writes +s+ to the stream. +s+ will be converted to a String using
|
||
# String#to_s.
|
ext/openssl/ossl_ssl.c | ||
---|---|---|
}
|
||
static VALUE
|
||
ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
|
||
ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock, int no_exception)
|
||
{
|
||
SSL *ssl;
|
||
int ilen, nread = 0;
|
||
... | ... | |
case SSL_ERROR_NONE:
|
||
goto end;
|
||
case SSL_ERROR_ZERO_RETURN:
|
||
if (no_exception) { return Qnil; }
|
||
rb_eof_error();
|
||
case SSL_ERROR_WANT_WRITE:
|
||
if (no_exception) { return ID2SYM(rb_intern("write_would_block")); }
|
||
write_would_block(nonblock);
|
||
rb_io_wait_writable(FPTR_TO_FD(fptr));
|
||
continue;
|
||
case SSL_ERROR_WANT_READ:
|
||
if (no_exception) { return ID2SYM(rb_intern("read_would_block")); }
|
||
read_would_block(nonblock);
|
||
rb_io_wait_readable(FPTR_TO_FD(fptr));
|
||
continue;
|
||
case SSL_ERROR_SYSCALL:
|
||
if(ERR_peek_error() == 0 && nread == 0) rb_eof_error();
|
||
if(ERR_peek_error() == 0 && nread == 0) {
|
||
if (no_exception) { return Qnil; }
|
||
rb_eof_error();
|
||
}
|
||
rb_sys_fail(0);
|
||
default:
|
||
ossl_raise(eSSLError, "SSL_read:");
|
||
... | ... | |
static VALUE
|
||
ossl_ssl_read(int argc, VALUE *argv, VALUE self)
|
||
{
|
||
return ossl_ssl_read_internal(argc, argv, self, 0);
|
||
return ossl_ssl_read_internal(argc, argv, self, 0, 0);
|
||
}
|
||
/*
|
||
... | ... | |
static VALUE
|
||
ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self)
|
||
{
|
||
return ossl_ssl_read_internal(argc, argv, self, 1);
|
||
return ossl_ssl_read_internal(argc, argv, self, 1, 0);
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* ssl.try_sysread_nonblock(length) => string, :write_would_block,
|
||
* :read_would_block, or nil (for EOF)
|
||
* ssl.try_sysread_nonblock(length, buffer) => buffer, :write_would_block,
|
||
* :read_would_block, or nil (for EOF)
|
||
*
|
||
* Exactly the same as +sysread_nonblock+, except that instead of raising
|
||
* exceptions for EOF or when the read would block, it returns nil,
|
||
* :read_would_block or :write_would_block.
|
||
*/
|
||
static VALUE
|
||
ossl_ssl_try_read_nonblock(int argc, VALUE *argv, VALUE self)
|
||
{
|
||
return ossl_ssl_read_internal(argc, argv, self, 1, 1);
|
||
}
|
||
static VALUE
|
||
ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock)
|
||
ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock, int no_exception)
|
||
{
|
||
SSL *ssl;
|
||
int nwrite = 0;
|
||
... | ... | |
case SSL_ERROR_NONE:
|
||
goto end;
|
||
case SSL_ERROR_WANT_WRITE:
|
||
if (no_exception) { return ID2SYM(rb_intern("write_would_block")); }
|
||
write_would_block(nonblock);
|
||
rb_io_wait_writable(FPTR_TO_FD(fptr));
|
||
continue;
|
||
case SSL_ERROR_WANT_READ:
|
||
if (no_exception) { return ID2SYM(rb_intern("read_would_block")); }
|
||
read_would_block(nonblock);
|
||
rb_io_wait_readable(FPTR_TO_FD(fptr));
|
||
continue;
|
||
... | ... | |
static VALUE
|
||
ossl_ssl_write(VALUE self, VALUE str)
|
||
{
|
||
return ossl_ssl_write_internal(self, str, 0);
|
||
return ossl_ssl_write_internal(self, str, 0, 0);
|
||
}
|
||
/*
|
||
... | ... | |
static VALUE
|
||
ossl_ssl_write_nonblock(VALUE self, VALUE str)
|
||
{
|
||
return ossl_ssl_write_internal(self, str, 1);
|
||
return ossl_ssl_write_internal(self, str, 1, 0);
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* ssl.syswrite_nonblock(string) => Integer, :read_would_block or
|
||
* :write_would_block
|
||
*
|
||
* Exactly the same as +syswrite_nonblock+, except that instead of
|
||
* raising an exception if the write would block, returns
|
||
* :read_would_block or :write_would_block.
|
||
*/
|
||
static VALUE
|
||
ossl_ssl_try_write_nonblock(VALUE self, VALUE str)
|
||
{
|
||
return ossl_ssl_write_internal(self, str, 1, 1);
|
||
}
|
||
/*
|
||
... | ... | |
rb_define_method(cSSLSocket, "accept_nonblock", ossl_ssl_accept_nonblock, 0);
|
||
rb_define_method(cSSLSocket, "sysread", ossl_ssl_read, -1);
|
||
rb_define_private_method(cSSLSocket, "sysread_nonblock", ossl_ssl_read_nonblock, -1);
|
||
rb_define_private_method(cSSLSocket, "try_sysread_nonblock", ossl_ssl_try_read_nonblock, -1);
|
||
rb_define_method(cSSLSocket, "syswrite", ossl_ssl_write, 1);
|
||
rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, 1);
|
||
rb_define_private_method(cSSLSocket, "try_syswrite_nonblock", ossl_ssl_try_write_nonblock, 1);
|
||
rb_define_method(cSSLSocket, "sysclose", ossl_ssl_close, 0);
|
||
rb_define_method(cSSLSocket, "cert", ossl_ssl_get_cert, 0);
|
||
rb_define_method(cSSLSocket, "peer_cert", ossl_ssl_get_peer_cert, 0);
|
ext/stringio/stringio.c | ||
---|---|---|
static VALUE
|
||
strio_sysread(int argc, VALUE *argv, VALUE self)
|
||
{
|
||
if (argc == 0) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)", argc); }
|
||
VALUE val = strio_read(argc, argv, self);
|
||
if (NIL_P(val)) {
|
||
rb_eof_error();
|
||
... | ... | |
return val;
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* strio.sysread(integer[, outbuf]) -> string or nil
|
||
*
|
||
* Exactly the same as +sysread+, except that instead of raising an
|
||
* EOFError at EOF, returns nil. This matches the +read_nonblock+
|
||
* protocol from the IO class.
|
||
*/
|
||
static VALUE
|
||
strio_try_sysread(int argc, VALUE *argv, VALUE self)
|
||
{
|
||
if (argc == 0) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)", argc); }
|
||
VALUE val = strio_read(argc, argv, self);
|
||
if (NIL_P(val)) { return Qnil; }
|
||
return val;
|
||
}
|
||
#define strio_syswrite strio_write
|
||
/*
|
||
... | ... | |
rb_define_method(StringIO, "sysread", strio_sysread, -1);
|
||
rb_define_method(StringIO, "readpartial", strio_sysread, -1);
|
||
rb_define_method(StringIO, "read_nonblock", strio_sysread, -1);
|
||
rb_define_method(StringIO, "try_read_nonblock", strio_try_sysread, -1);
|
||
rb_define_method(StringIO, "write", strio_write, 1);
|
||
rb_define_method(StringIO, "<<", strio_addstr, 1);
|
io.c | ||
---|---|---|
}
|
||
static VALUE
|
||
io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock)
|
||
io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock, int no_exception)
|
||
{
|
||
rb_io_t *fptr;
|
||
VALUE length, str;
|
||
... | ... | |
if (n < 0) {
|
||
if (!nonblock && rb_io_wait_readable(fptr->fd))
|
||
goto again;
|
||
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN))
|
||
rb_mod_sys_fail(rb_mWaitReadable, "read would block");
|
||
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
|
||
if (no_exception)
|
||
return ID2SYM(rb_intern("read_would_block"));
|
||
else
|
||
rb_mod_sys_fail(rb_mWaitReadable, "read would block");
|
||
}
|
||
rb_sys_fail_path(fptr->pathv);
|
||
}
|
||
}
|
||
... | ... | |
{
|
||
VALUE ret;
|
||
ret = io_getpartial(argc, argv, io, 0);
|
||
ret = io_getpartial(argc, argv, io, 0, 0);
|
||
if (NIL_P(ret))
|
||
rb_eof_error();
|
||
else
|
||
... | ... | |
{
|
||
VALUE ret;
|
||
ret = io_getpartial(argc, argv, io, 1);
|
||
ret = io_getpartial(argc, argv, io, 1, 0);
|
||
if (NIL_P(ret))
|
||
rb_eof_error();
|
||
else
|
||
return ret;
|
||
}
|
||
/**
|
||
* call-seq:
|
||
* ios.try_read_nonblock(maxlen) -> string, nil, or :read_would_block
|
||
* ios.try_read_nonblock(maxlen, outbuf) -> outbuf, nil, or :read_would_block
|
||
*
|
||
* +try_read_nonblock+ is identical to +read_nonblock+,
|
||
* except that instead of raising exceptions, blocking
|
||
* calls will return :read_would_block, and EOF will
|
||
* return nil.
|
||
*/
|
||
static VALUE
|
||
io_try_read_nonblock(int argc, VALUE *argv, VALUE io)
|
||
{
|
||
VALUE ret;
|
||
ret = io_getpartial(argc, argv, io, 1, 1);
|
||
if (NIL_P(ret))
|
||
return Qnil;
|
||
else
|
||
return ret;
|
||
}
|
||
static VALUE
|
||
io_write_nonblock(VALUE io, VALUE str, int no_exception)
|
||
{
|
||
rb_io_t *fptr;
|
||
long n;
|
||
rb_secure(4);
|
||
if (TYPE(str) != T_STRING)
|
||
str = rb_obj_as_string(str);
|
||
io = GetWriteIO(io);
|
||
GetOpenFile(io, fptr);
|
||
rb_io_check_writable(fptr);
|
||
if (io_fflush(fptr) < 0)
|
||
rb_sys_fail(0);
|
||
rb_io_set_nonblock(fptr);
|
||
n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str));
|
||
if (n == -1) {
|
||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||
if (no_exception) return ID2SYM(rb_intern("write_would_block"));
|
||
rb_mod_sys_fail(rb_mWaitWritable, "write would block");
|
||
}
|
||
rb_sys_fail_path(fptr->pathv);
|
||
}
|
||
return LONG2FIX(n);
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* ios.write_nonblock(string) -> integer
|
||
... | ... | |
static VALUE
|
||
rb_io_write_nonblock(VALUE io, VALUE str)
|
||
{
|
||
rb_io_t *fptr;
|
||
long n;
|
||
rb_secure(4);
|
||
if (TYPE(str) != T_STRING)
|
||
str = rb_obj_as_string(str);
|
||
io = GetWriteIO(io);
|
||
GetOpenFile(io, fptr);
|
||
rb_io_check_writable(fptr);
|
||
if (io_fflush(fptr) < 0)
|
||
rb_sys_fail(0);
|
||
rb_io_set_nonblock(fptr);
|
||
n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str));
|
||
if (n == -1) {
|
||
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
||
rb_mod_sys_fail(rb_mWaitWritable, "write would block");
|
||
rb_sys_fail_path(fptr->pathv);
|
||
}
|
||
return io_write_nonblock(io, str, 0);
|
||
}
|
||
return LONG2FIX(n);
|
||
/*
|
||
* call-seq:
|
||
* ios.try_write_nonblock(string) -> integer or :write_would_block
|
||
*
|
||
* Works exactly like write_nonblock, with one exception:
|
||
*
|
||
* * if the write would block, <code>try_write_nonblock</code> returns
|
||
* :write_would_block rather than raising IO::WaitWritable
|
||
*/
|
||
static VALUE
|
||
rb_io_try_write_nonblock(VALUE io, VALUE str)
|
||
{
|
||
return io_write_nonblock(io, str, 1);
|
||
}
|
||
/*
|
||
... | ... | |
RUBY_METHOD_FUNC(0), Qnil, rb_eEOFError, (VALUE)0);
|
||
}
|
||
else {
|
||
tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock);
|
||
tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock, 0);
|
||
}
|
||
if (NIL_P(tmp)) {
|
||
if (ARGF.next_p == -1) {
|
||
... | ... | |
rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1);
|
||
rb_define_method(rb_cIO, "read_nonblock", io_read_nonblock, -1);
|
||
rb_define_method(rb_cIO, "try_read_nonblock", io_try_read_nonblock, -1);
|
||
rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, 1);
|
||
rb_define_method(rb_cIO, "try_write_nonblock", rb_io_try_write_nonblock, 1);
|
||
rb_define_method(rb_cIO, "readpartial", io_readpartial, -1);
|
||
rb_define_method(rb_cIO, "read", io_read, -1);
|
||
rb_define_method(rb_cIO, "write", io_write_m, 1);
|
test/openssl/test_pair.rb | ||
---|---|---|
ret = nil
|
||
assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10) }
|
||
assert_equal("def\n", ret)
|
||
s1.close
|
||
assert_raise(EOFError) { s2.read_nonblock(10) }
|
||
}
|
||
end
|
||
def test_try_read_nonblock
|
||
ssl_pair {|s1, s2|
|
||
assert_equal :read_would_block, s2.try_read_nonblock(10)
|
||
s1.write "abc\ndef\n"
|
||
IO.select([s2])
|
||
assert_equal("ab", s2.try_read_nonblock(2))
|
||
assert_equal("c\n", s2.gets)
|
||
ret = nil
|
||
assert_nothing_raised("[ruby-core:20298]") { ret = s2.try_read_nonblock(10) }
|
||
assert_equal("def\n", ret)
|
||
s1.close
|
||
assert_equal(nil, s2.try_read_nonblock(10))
|
||
}
|
||
end
|
||
def write_nonblock(socket, meth, str)
|
||
ret = socket.send(meth, str)
|
||
ret.is_a?(Symbol) ? 0 : ret
|
||
end
|
||
def test_write_nonblock
|
||
ssl_pair {|s1, s2|
|
||
n = 0
|
||
begin
|
||
n += s1.write_nonblock("a" * 100000)
|
||
n += s1.write_nonblock("b" * 100000)
|
||
n += s1.write_nonblock("c" * 100000)
|
||
n += s1.write_nonblock("d" * 100000)
|
||
n += s1.write_nonblock("e" * 100000)
|
||
n += s1.write_nonblock("f" * 100000)
|
||
n += write_nonblock s1, :write_nonblock, "a" * 100000
|
||
n += write_nonblock s1, :write_nonblock, "b" * 100000
|
||
n += write_nonblock s1, :write_nonblock, "c" * 100000
|
||
n += write_nonblock s1, :write_nonblock, "d" * 100000
|
||
n += write_nonblock s1, :write_nonblock, "e" * 100000
|
||
n += write_nonblock s1, :write_nonblock, "f" * 100000
|
||
rescue IO::WaitWritable
|
||
end
|
||
s1.close
|
||
... | ... | |
}
|
||
end
|
||
def test_try_write_nonblock
|
||
ssl_pair {|s1, s2|
|
||
n = 0
|
||
n += write_nonblock s1, :try_write_nonblock, "a" * 100000
|
||
n += write_nonblock s1, :try_write_nonblock, "b" * 100000
|
||
n += write_nonblock s1, :try_write_nonblock, "c" * 100000
|
||
n += write_nonblock s1, :try_write_nonblock, "d" * 100000
|
||
n += write_nonblock s1, :try_write_nonblock, "e" * 100000
|
||
n += write_nonblock s1, :try_write_nonblock, "f" * 100000
|
||
s1.close
|
||
assert_equal(n, s2.read.length)
|
||
}
|
||
end
|
||
def test_write_nonblock_with_buffered_data
|
||
ssl_pair {|s1, s2|
|
||
s1.write "foo"
|
||
... | ... | |
}
|
||
end
|
||
def test_try_write_nonblock_with_buffered_data
|
||
ssl_pair {|s1, s2|
|
||
s1.write "foo"
|
||
s1.try_write_nonblock("bar")
|
||
s1.write "baz"
|
||
s1.close
|
||
assert_equal("foobarbaz", s2.read)
|
||
}
|
||
end
|
||
def test_connect_accept_nonblock
|
||
host = "127.0.0.1"
|
||
port = 0
|
test/ruby/test_io.rb | ||
---|---|---|
end)
|
||
end
|
||
def test_try_write_nonblock
|
||
skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||
pipe(proc do |w|
|
||
w.try_write_nonblock(1)
|
||
w.close
|
||
end, proc do |r|
|
||
assert_equal("1", r.read)
|
||
end)
|
||
end
|
||
def test_read_nonblock_error
|
||
return if !have_nonblock?
|
||
skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||
... | ... | |
assert_kind_of(IO::WaitReadable, $!)
|
||
end
|
||
}
|
||
with_pipe {|r, w|
|
||
begin
|
||
r.read_nonblock 4096, ""
|
||
rescue Errno::EWOULDBLOCK
|
||
assert_kind_of(IO::WaitReadable, $!)
|
||
end
|
||
}
|
||
end
|
||
def test_try_read_nonblock
|
||
return if !have_nonblock?
|
||
skip "IO#try_read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||
with_pipe {|r, w|
|
||
assert_equal :read_would_block, r.try_read_nonblock(4096)
|
||
w.puts "HI!"
|
||
assert_equal "HI!\n", r.try_read_nonblock(4096)
|
||
w.close
|
||
assert_equal nil, r.try_read_nonblock(4096)
|
||
}
|
||
end
|
||
def test_try_read_nonblock_with_buffer
|
||
return if !have_nonblock?
|
||
skip "IO#try_read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||
with_pipe {|r, w|
|
||
assert_equal :read_would_block, r.try_read_nonblock(4096, "")
|
||
w.puts "HI!"
|
||
buf = "buf"
|
||
value = r.try_read_nonblock(4096, buf)
|
||
assert_equal value, "HI!\n"
|
||
assert buf.equal?(value)
|
||
w.close
|
||
assert_equal nil, r.try_read_nonblock(4096, "")
|
||
}
|
||
end
|
||
def test_write_nonblock_error
|
||
... | ... | |
}
|
||
end
|
||
def test_try_write_nonblock
|
||
return if !have_nonblock?
|
||
skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
||
with_pipe {|r, w|
|
||
loop {
|
||
ret = w.try_write_nonblock "a"*100000
|
||
if ret.is_a?(Symbol)
|
||
assert ret == :write_would_block
|
||
break
|
||
end
|
||
}
|
||
}
|
||
end
|
||
def test_gets
|
||
pipe(proc do |w|
|
||
w.write "foobarbaz"
|
test/socket/test_nonblock.rb | ||
---|---|---|
s.close if s
|
||
end
|
||
def test_try_read_nonblock
|
||
c, s = tcp_pair
|
||
assert_equal :read_would_block, c.try_read_nonblock(100)
|
||
assert_equal :read_would_block, s.try_read_nonblock(100)
|
||
c.write("abc")
|
||
IO.select [s]
|
||
assert_equal("a", s.try_read_nonblock(1))
|
||
assert_equal("bc", s.try_read_nonblock(100))
|
||
assert_equal :read_would_block, s.try_read_nonblock(100)
|
||
ensure
|
||
c.close if c
|
||
s.close if s
|
||
end
|
||
=begin
|
||
def test_write_nonblock
|
||
c, s = tcp_pair
|
test/stringio/test_stringio.rb | ||
---|---|---|
f = StringIO.new("\u3042\u3044")
|
||
assert_raise(ArgumentError) { f.readpartial(-1) }
|
||
assert_raise(ArgumentError) { f.readpartial(1, 2, 3) }
|
||
assert_equal("\u3042\u3044", f.readpartial)
|
||
assert_raise(ArgumentError) { f.readpartial }
|
||
assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.readpartial(100))
|
||
f.rewind
|
||
assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.readpartial(f.size))
|
||
end
|
||
... | ... | |
f = StringIO.new("\u3042\u3044")
|
||
assert_raise(ArgumentError) { f.read_nonblock(-1) }
|
||
assert_raise(ArgumentError) { f.read_nonblock(1, 2, 3) }
|
||
assert_equal("\u3042\u3044", f.read_nonblock)
|
||
assert_raise(ArgumentError) { f.read_nonblock }
|
||
assert_equal("\u3042\u3044".force_encoding("BINARY"), f.read_nonblock(100))
|
||
assert_raise(EOFError) { f.read_nonblock(10) }
|
||
f.rewind
|
||
assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(f.size))
|
||
end
|
||
def test_try_read_nonblock
|
||
f = StringIO.new("\u3042\u3044")
|
||
assert_raise(ArgumentError) { f.try_read_nonblock(-1) }
|
||
assert_raise(ArgumentError) { f.try_read_nonblock(1, 2, 3) }
|
||
assert_raise(ArgumentError) { f.try_read_nonblock }
|
||
assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.try_read_nonblock(100))
|
||
assert_equal(nil, f.try_read_nonblock(10))
|
||
f.rewind
|
||
assert_equal("\u3042\u3044".force_encoding(Encoding::ASCII_8BIT), f.read_nonblock(f.size))
|
||
end
|