Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

uv_write throws when trying to send 3GB over HTTP connection #54225

Closed
simsurace opened this issue Apr 24, 2024 · 3 comments · Fixed by #54233
Closed

uv_write throws when trying to send 3GB over HTTP connection #54225

simsurace opened this issue Apr 24, 2024 · 3 comments · Fixed by #54233

Comments

@simsurace
Copy link
Contributor

simsurace commented Apr 24, 2024

I encountered some problem JuliaWeb/HTTP.jl#1171 which seems to be an issue in Base when trying to send a large payload over an HTTP connection.

using HTTP

function get_data(::HTTP.Request)
    data = rand(UInt8, 3_000_000_000)
    return HTTP.Response(200, data)
end

const ROUTER = HTTP.Router()
HTTP.register!(ROUTER, "GET", "/", get_data)
server = HTTP.serve!(ROUTER, "127.0.0.1", 8008)

When a request is made, the server throws the error below. The same code works when doing 2GB instead of 3GB.

julia> ┌ Error: handle_connection handler error. 
│ 
│ ===========================
│ HTTP Error message:
│ 
│ ERROR: IOError: write: invalid argument (EINVAL)
│ Stacktrace:
│   [1] uv_write(s::Sockets.TCPSocket, p::Ptr{UInt8}, n::UInt64)
│     @ Base ./stream.jl:1066
│   [2] unsafe_write(s::Sockets.TCPSocket, p::Ptr{UInt8}, n::UInt64)
│     @ Base ./stream.jl:1120
│   [3] unsafe_write
│     @ ~/.julia/packages/HTTP/vnQzp/src/Connections.jl:129 [inlined]
│   [4] unsafe_write(http::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.Connections.Connection{Sockets.TCPSocket}}, p::Ptr{UInt8}, n::UInt64)
│     @ HTTP.Streams ~/.julia/packages/HTTP/vnQzp/src/Streams.jl:95
│   [5] unsafe_write
│     @ ./io.jl:698 [inlined]
│   [6] write(s::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.Connections.Connection{Sockets.TCPSocket}}, a::Vector{UInt8})
│     @ Base ./io.jl:721
│   [7] (::HTTP.Handlers.var"#1#2"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}})(stream::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.Connections.Connection{Sockets.TCPSocket}})
│     @ HTTP.Handlers ~/.julia/packages/HTTP/vnQzp/src/Handlers.jl:61
│   [8] #invokelatest#2
│     @ ./essentials.jl:892 [inlined]
│   [9] invokelatest
│     @ ./essentials.jl:889 [inlined]
│  [10] handle_connection(f::Function, c::HTTP.Connections.Connection{Sockets.TCPSocket}, listener::HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, readtimeout::Int64, access_log::Nothing)
│     @ HTTP.Servers ~/.julia/packages/HTTP/vnQzp/src/Servers.jl:469
│  [11] (::HTTP.Servers.var"#16#17"{HTTP.Handlers.var"#1#2"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}}, HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, Set{HTTP.Connections.Connection}, Int64, Nothing, ReentrantLock, Base.Semaphore, HTTP.Connections.Connection{Sockets.TCPSocket}})()
│     @ HTTP.Servers ~/.julia/packages/HTTP/vnQzp/src/Servers.jl:401
└ @ HTTP.Servers ~/.julia/packages/HTTP/vnQzp/src/Servers.jl:483

Tested on

  • Julia 1.10.2
  • HTTP.jl 1.10.5
  • MbedTLS.jl 1.1.9
@oscardssmith
Copy link
Member

Trying this with Julia 1.10.2, HTTP.jl 1.10.6 and MbedTLS.jl 1.1.9 worked for me (x86 linux). What does versioninfo() say?

@vtjnash
Copy link
Sponsor Member

vtjnash commented Apr 24, 2024

We used to have handling for this, but looks like we dropped that commit from libuv at some point:

$ git log origin/julia-uv2-1.24.0  --grep 'limit unix read'
commit aa6ef9a3f17184b6e4b2c9ac8be1a020177b337a
Author: Amit Murthy <amit.murthy@gmail.com>
Date:   Mon Oct 19 12:27:27 2015 +0530

    stream: limit unix read/write to 2GB at a time
    
    On some platforms, notably OSX, read/readv/write/writev all return
    an EINVAL if the requested number of bytes is greater than INT32_MAX.
    
    We therefore limit each such call to 2GB on all platforms. On
    platforms where this is not an issue, the overhead of an extra syscall
    for every 2GB of read/write is negligible.
$ git log origin/julia-uv2-1.29.1 --grep 'limit unix read'

That had a libuv PR: libuv/libuv#1501
We also have support for fixing this in Julia, but it is disabled here on Unix (this should be <= typemax(Int32) - pagesize):

julia/base/stream.jl

Lines 121 to 125 in e644ebd

# manually limit our write size, if the OS doesn't support full-size writes
if Sys.iswindows()
const MAX_OS_WRITE = UInt(0x1FF0_0000) # 511 MB (determined semi-empirically, limited to 31 MB on XP)
else
const MAX_OS_WRITE = UInt(typemax(Csize_t))

vtjnash added a commit that referenced this issue Apr 24, 2024
@simsurace
Copy link
Contributor Author

Yeah exactly, I tested this on Mac OS (Apple silicon if it matters).

julia> versioninfo()
Julia Version 1.10.2
Commit bd47eca2c8a (2024-03-01 10:14 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: macOS (arm64-apple-darwin22.4.0)
  CPU: 12 × Apple M3 Pro
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, apple-m1)
Threads: 1 default, 0 interactive, 1 GC (on 6 virtual cores)
Environment:
  JULIA_EDITOR = code

KristofferC pushed a commit that referenced this issue Apr 25, 2024
KristofferC pushed a commit that referenced this issue Apr 25, 2024
Fixes #54225

(cherry picked from commit b35d308)
KristofferC pushed a commit that referenced this issue May 8, 2024
Fixes #54225

(cherry picked from commit b35d308)
KristofferC pushed a commit that referenced this issue May 25, 2024
Fixes #54225

(cherry picked from commit b35d308)
KristofferC pushed a commit that referenced this issue May 25, 2024
Fixes #54225

(cherry picked from commit b35d308)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants