uWebSockets icon indicating copy to clipboard operation
uWebSockets copied to clipboard

use with sendfile?

Open LumiereDeLOmbre opened this issue 2 years ago • 9 comments

Hi, Reading the code, I wonder how I could use the sendfile syscall in the app.

My usecase is the distribution of static files and I would like to avoid copies in user space.

Linux. From those in the know, any issues to predict with using sendfile in uws?

Thanks

LumiereDeLOmbre avatar Mar 12 '22 07:03 LumiereDeLOmbre

If you have static files I would just pick any optimized standalone web server. This library is mainly WebSockets oriented (small message sending). A web server with sendfile + KTLS would probably perform better than uWS for those huge file sending cases (but do not assume it will - many web servers are extremely slow either way).

ghost avatar Mar 12 '22 18:03 ghost

Thanks Alex, I still need your functionalities for managing sessions, security and al. Sending the file is just at the end of the chain for some requests( 80%).

Looking at your code I will need to modify HttpResponse.h to bypass your strategy (corking..) and letting sendfile do all the heavy lifting? Is there something else I shoul dbe aware of Alex?

Thanks for your great work, it is a pleasure to work with.

ghost avatar Mar 12 '22 20:03 ghost

Corking only kicks in for small message sending, the head of your response. The body is uncorked. Corking is very useful for the head, as it smashes together the headers into 1 send call.

The only thing you need to look at for sendfile, is the code that runs in onWritable - that's the only place where sendfile would improve things. Instead of doing send in onWritable, you do sendfile.

ghost avatar Mar 12 '22 21:03 ghost

Ok I will give a look. If the result is good enough, do you want a PR?

ghost avatar Mar 12 '22 21:03 ghost

If you can make it as a small addition to HttpResponse.h only (pretty sure that is possible), where the corking of the first head is preserved as is.

Something like tryEndWithFile or something. Look at how tryEnd works and how tryEnd works inside onWritable. Imagine swapping that for tryEndWithFile

ghost avatar Mar 12 '22 21:03 ghost

Will do Alex 👍

ghost avatar Mar 12 '22 21:03 ghost

i get about 5.8 GB/s using sendfile with uWebSockets on linux

I haven't optimized this.

pub fn onSendfile(this: *RequestContext) bool {
    const adjusted_count_temporary = @minimum(@as(u64, this.sendfile.remain), @as(u63, std.math.maxInt(u63)));
    // TODO we should not need this int cast; improve the return type of `@minimum`
    const adjusted_count = @intCast(u63, adjusted_count_temporary);

    if (Environment.isLinux) {
        var signed_offset = @intCast(i64, this.sendfile.offset);
        const start = this.sendfile.offset;
        const val =
            // this does the syscall directly, without libc
            linux.sendfile(this.sendfile.socket_fd, this.sendfile.fd, &signed_offset, this.sendfile.remain);
        this.sendfile.offset = @intCast(Blob.SizeType, signed_offset);

        const errcode = linux.getErrno(val);

        this.sendfile.remain -= @intCast(Blob.SizeType, this.sendfile.offset - start);

        if (errcode != .SUCCESS or this.aborted or this.sendfile.remain == 0 or val == 0) {
            if (errcode != .SUCCESS) {
                Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
                Output.flush();
            }
            this.cleanupAfterSendfile();
            return errcode != .SUCCESS;
        }
    } else {
        var sbytes: std.os.off_t = adjusted_count;
        const signed_offset = @bitCast(i64, @as(u64, this.sendfile.offset));

        const errcode = std.c.getErrno(std.c.sendfile(
            this.sendfile.fd,
            this.sendfile.socket_fd,

            signed_offset,
            &sbytes,
            null,
            0,
        ));
        const wrote = @intCast(Blob.SizeType, sbytes);
        this.sendfile.offset += wrote;
        this.sendfile.remain -= wrote;
        if (errcode != .AGAIN or this.aborted or this.sendfile.remain == 0 or sbytes == 0) {
            if (errcode != .AGAIN and errcode != .SUCCESS) {
                Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
                Output.flush();
            }
            this.cleanupAfterSendfile();
            return errcode != .SUCCESS;
        }
    }

    if (!this.sendfile.has_set_on_writable) {
        this.sendfile.has_set_on_writable = true;
        this.resp.onWritable(*RequestContext, onWritableSendfile, this);
    }

    this.resp.markNeedsMore();

    return true;
}

Jarred-Sumner avatar Mar 24 '22 01:03 Jarred-Sumner

@LeHibou2 Did you get it working?

mtsewrs avatar Jun 10 '22 09:06 mtsewrs

@LeHibou2 Did you get it working?

Well I didn't work on it since I had so many things going on, but I am coming at a point (end of the month) where I will need it so I will report my implementation here (no PR) and Alex and others could improve it if they want. 👍

ghost avatar Jun 10 '22 14:06 ghost