https://github.com/scarfacedeb/exfile_sendfile_example
Sample app to demostrate Exfile and Briefly bugs with sendfile.
https://github.com/scarfacedeb/exfile_sendfile_example
Last synced: 2 months ago
JSON representation
Sample app to demostrate Exfile and Briefly bugs with sendfile.
- Host: GitHub
- URL: https://github.com/scarfacedeb/exfile_sendfile_example
- Owner: scarfacedeb
- Created: 2019-06-18T14:24:46.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2019-09-14T09:12:50.000Z (over 5 years ago)
- Last Synced: 2025-03-14T05:02:12.564Z (3 months ago)
- Language: Elixir
- Homepage:
- Size: 21.5 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ExfileSendfile
Sample app to demostrate Exfile and Briefly bugs with sendfile.
## Running the tests
Start the application:
```
iex -s mix
```Make a request in the other shell. To have more consistent results, restart the application after every request.
### Exfile
To test Exfile.Tempfile:
```
curl --limit-rate 500 "localhost:4422/exfile/5000" | tail -c 5
```### Briefly
To test Briefly package that has similar implementation of temp files to Exfile.Tempfile:
```
curl --limit-rate 500 "localhost:4422/briefly/5000" | tail -c 5
```### Stripped down implementation
To test stripped down minimal implementation of Temp files
```
curl --limit-rate 500 "localhost:4422/custom/5000" | tail -c 5
```### Fixed version
To test the fixed version of stripped down implementation:
```
curl --limit-rate 500 "localhost:4422/fixed/5000" | tail -c 5
```It's the only version that should consistently response without any errors.
## Results
### First run
You should get a similar results after running any of the tests (except for the fixed one).
Curl will report that it haven't received any body:
```
scarfacedeb@scarfacedeb-macbook-pro-4 exfile_sendfile % curl --limit-rate 500 "localhost:4422/exfile/5000" | tail -c 5
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 5000 0 0 0 0 0 0 --:--:-- 0:00:05 --:--:-- 0
curl: (18) transfer closed with 5000 bytes remaining to read
```Application logs will report that the random file has been deleted before the connection shutdown:
```
2019-09-12 19:55:47.624 [info] GET /exfile/5000
2019-09-12 19:55:47.649 [info] label=self #PID<0.396.0>
2019-09-12 19:55:47.649 [info] label=connection_pid #PID<0.395.0>
2019-09-12 19:55:47.650 [info] Sent 200 in 25ms
2019-09-12 19:55:47.650 [info] >> End request
2019-09-12 19:55:47.650 [info] label=request {:DOWN, #Reference<0.105115889.1320681474.11443>, :process, #PID<0.396.0>, :normal}
2019-09-12 19:55:47.650 [info] label=File exists? false
2019-09-12 19:55:52.654 [info] label=connection {:DOWN, #Reference<0.105115889.1320681474.11445>, :process, #PID<0.395.0>, {:shutdown, {:connection_error, :timeout, :"No request-line received before timeout."}}}
2019-09-12 19:55:52.654 [info] label=File exists? false
```You'll also notice that the request process terminated almost immediately, causing the temp file deletion.
### The second run
If you try to run it the second time, the request could succeed and you'll notice the trace message in the iex log:
```
2019-09-12 19:57:45.720 [info] GET /exfile/5000
2019-09-12 19:57:45.721 [info] label=self #PID<0.347.0>
2019-09-12 19:57:45.721 [info] label=connection_pid #PID<0.346.0>
2019-09-12 19:57:45.721 [info] Sent 200 in 884µs
2019-09-12 19:57:45.721 [info] >> End request
2019-09-12 19:57:45.721 [info] label=request {:DOWN, #Reference<0.2965400933.1320943617.2050>, :process, #PID<0.347.0>, :normal}
2019-09-12 19:57:45.721 [info] label=File exists? false
9/12/2019-16:57:45: #PID<0.346.0> >> :erlang.port_control/3 [port: #Port<0.7>, args: [<<48, 0, 0, 0>>, [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 19, 136]]]
2019-09-12 19:57:51.724 [info] label=connection {:DOWN, #Reference<0.2965400933.1320943617.2052>, :process, #PID<0.346.0>, {:shutdown, {:connection_error, :timeout, :"No request-line received before timeout."}}}
2019-09-12 19:57:51.724 [info] label=File exists? false
```It means that the connection process was able to send SENDFILE command to the TCP socket, before the request process died. And as expected, curl returned the response without any issues.
## Cause of the issue
The reason why Exfile and Briefly sometimes can't return return the response, because `:cowboy_req:reply` sends the sendfile process message to connection process, instead of request.
It means that while the connection process is still running and trying to call `:ranc_tcp.sendfile` with the random file path, request process terminates and it removes the random file at that moment.
## The fixed version
To fix the issue, you should monitor the connection process, instead of the request.
If you try the fixed route, you should always get the full response.
```
iex(1)> 2019-09-12 20:04:01.131 [info] GET /fixed/5000
File created
2019-09-12 20:04:01.155 [info] label=self #PID<0.352.0>
2019-09-12 20:04:01.155 [info] label=connection_pid #PID<0.351.0>
2019-09-12 20:04:01.156 [info] Sent 200 in 25ms
2019-09-12 20:04:01.156 [info] >> End request
2019-09-12 20:04:01.159 [info] label=request {:DOWN, #Reference<0.1169381897.516947973.227240>, :process, #PID<0.352.0>, :normal}
2019-09-12 20:04:01.159 [info] label=File exists? true
9/12/2019-17:4:1: #PID<0.351.0> >> :erlang.port_control/3 [port: #Port<0.19>, args: [<<50, 0, 0, 0>>, [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 19, 136]]]
2019-09-12 20:04:07.160 [info] label=connection {:DOWN, #Reference<0.1169381897.516947973.227242>, :process, #PID<0.351.0>, {:shutdown, {:connection_error, :timeout, :"No request-line received before timeout."}}}
File deleted!
2019-09-12 20:04:07.160 [info] label=File exists? false
```## Async sendfile
You could also notice that sometimes connection process is terminated before curl has finished downloading the file.
AFAIK, it's caused by the async nature of the sendfile system call. In short, it means that erlang doesn't stream the file directly, it sends sendfile command to tcp socket instead and finishes with the request.
You can test the socket command with `ExfileSendfile.SocketTest.run_and_exit/0`:
```
iex -s mix
iex(1)> ExfileSendfile.SocketTest.run_and_exit()
ok
iex(2)>
```It'll wait for the connection on 4433 port (by default):
```
curl --limit-rate 500 "localhost:4433" | tail -c 5
```Curl's limited rate allows to witness that it continues to download the data, despite the fact that the elixir OS process is already dead.