Phoenix 1.6 and VirtualBox shared folders and static content (need help diagnosing the issue)

First, my workflow: I use VSCodium on an Intel Mac to edit code in a folder that is shared with a vagrant VM running Debian using VirtualBox services. The folder is mounted using vboxsf in the vagrant VM with working symlinks. (Ask me how!!) Elixir and Phoenix code is running in the vagrant VM.

So, the problem:

When I create the same project on the vboxsf mounted directory structure, no static content is served. Chrome’s developer console warns me “Failed to load resource: net::ERR_CONTENT_LENGTH_MISMATCH” for app.css, app.js, and phoenix.png and they don’t load which makes developing LiveView not much fun. Interestingly, LiveDashboard with its inlined resources works fine.

When I create a new Phoenix 1.6.2 project in the vagrant VM’s own directory structure, the project serves static content (CSS, JS, pngs etc) correctly.

This particular problem didn’t occur when I was using Phoenix 1.5.x - although lots of other problems with npm installation errors did and I had a set of work-arounds for that. So glad to have esbuild now!

Any tips to diagnose what is going wrong? (I’m pretty sure it’s a VirtualBox problem, not actually Phoenix, but my Phoenix dev workflow is repeatably not working the way it should, so that’s where I’d like to start)

I moved Plug.Telemetry to above Plug.Static and I see 200 ok messages for the static content. Can I instrument Cowboy? How? I’m open to any ideas to save my workflow…


[Edit: a little progress]

After modifying the Plug.Static config in endpoint.ex to have an absolute path to a copy of the private/static directory that I placed within /tmp, Plug.Static started serving the content. It’s definitely being caused by something in the vboxsf filesystem, but what? Plug.Static thinks it’s successfully serving the content on a vboxsf filesystem, but wget says zero bytes are received.

Long story short:

The issue is that vboxsf breaks the guest operating system’s sendfile syscall - this syscall can speed up serving unmodified static content over a file descriptor significantly (but not useful with compression or TLS alas).

Plug.Static doesn’t see that the sendfile operation in Cowboy/Ranch somehow fails, so it reports 200 ok, even though zero bytes make it to the client.

I was ultimately able to verify this by hacking Ranch’s ranch_tcp.erl to replace sendfile with ranch_transport:sendfile, which is a more traditional “read file from disk and send it to a socket” mechanism.

Which leads me to the following question I’m going to ask on the forum - “How do I set the cowboy_http option sendfile: false in Phoenix config/dev.exs?”

Turning off sendfile can be done in dev.exs.

When configuring the endpoint in a stanza like:

config :app, App.Endpoint, 
  http: [
    ip: {0, 0, 0, 0},
    port: 4000,
    protocol_options: [sendfile: false]

The protocol_options, amongst other things, are shared with cowboy. This will decrease the performance of static file loads, but in my dev environment, that’s not a big concern.

1 Like