-
-
Notifications
You must be signed in to change notification settings - Fork 133
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
Avoid emitting error if upload was not done on closing request #83
Comments
This is a known issue, and various attempts have been made to dealing with it correctly: #81. Streams and async stuff are super tricky to get right, and part of the reason this has not been fixed sooner is that tests for certain types of errors (e.g. client disconnect at certain times in the processing) can be hard to write, and to add to the confusing the behavior in different Node.js versions can be completely different. Help on this is welcome, a solid fix might be only a few lines away if you know a lot about streams and errors. |
Hi, @jaydenseric. Many thanks for your prompt answer! I can see now that there's an on going debate on the subject. Appreciate you pointing me to it. At the risk of repeating some of the stuff you + collaborators may have already covered:
|
Follow up on first point above:
The way I see it, any option is preferable before letting a server collapse. |
Best place to start is to PR a new test that demonstrates the issue, then we can look at specific fixes. In the situation where a client sends a multipart request with a file for an argument that is meant to be some other type such as a string, I would expect the upload middleware to throw an error that can either be caught in middleware or be allowed through to send an bad request HTTP error response back to the client. Without a server crash! |
Or any other argument that's not an
I understand where you're coming from, but I think the problem is pretty well "demonstrated" at this point. And writing a test covering all cases might not be trivial (a hunch, to be honest). As per my question above, I'm wondering if this isn't a self imposed issue: why are we emitting errors in the first place, any way? What's the benefit? |
The purpose of the test is not to explain the issue, but to give us a tool to work out a solution. Once the test passes, we know the problem is solved. At the moment you are the best person to write that test, as you understand the particular issue as a user and are motivated to find a solution. Otherwise I will have to write it, which eats into solution time 😓 The |
Agreed. Not debating the general usefulness or errors. But, honestly, that's just not what happens in the current implementation. Right now,
My main point here is: why do we choose this vs. just ignoring the error/consuming the stream/any other solution? Is it per spec? Is it some kind of architectural design approach? I fail see how the
Gotcha. I'll try to come up with something and will let you know. |
❤️ Certain errors causing server crashes are a known issue: #45. We still want meaningful errors, just not server crashes. I think we can fix the server crash (being worked on). Because the upload middleware is ignorant about the GraphQL schema (graphql middleware comes after) it might be hard or undesirable to prevent errors if people send valid multipart GraphQL requests, with invalid use of fields according to the schema. I don't think our middleware can check if error listeners from the resolvers are attached yet before emitting errors, as the error might happen before resolver code has run yet? |
Hmmm, maybe we can. It's probably safe to assume that if there are no listeners yet nothing happened that needs handling/cleanup. |
Ah yes, this is actually the approach I took with #80. Working on that test actually exposed some other issues, and I decided to roll it into #81, where it’s addressed a bit more elegantly. Something else to note is that Also keep in mind that the test in #81 is far better, as the fixtures in #80 can fit in a single TCP packet and cause the test pass when it shouldn’t. The improved test is currently skipped, as tap keeps calling it a failure even though all sub-tests pass, and the code behaves correctly in manual real-world tests. |
👍 I believe that's aligned with my suggestion above of allowing some other form of error subscription/emission. Good one.
Some things to consider here (if we haven't already):
|
I don't think we should add any sort of |
@jaydenseric The scenario we are dealing with here prevents those cases to actually occur:
I don't see your proposed design as a general fit for the middleware. Are you thinking about something else? |
We already have a mechanism for middleware errors outside of resolvers, see the test for |
So, I think we have some nomenclature to clarify here. Koa will catch any errors that are thrown, or promises that are rejected as part of the middleware chain. But that’s not the only situation we have here, since an emitted error exists outside this chain, unless something inside is already listening for it, and throws/rejects in response. The issue is that an error can happen before a resolver has attached handlers, and when an EventEmitter emits an unhandled “error” event, node terminates the process (crashes the app), and koa can’t do anything to prevent it. Also, these streams are created by busboy as part of the “file” event, which is inaccessible to the user. Therefore, the apollo-upload-server is really the only place we can confidently attach a handler to prevent premature exits. @nfantone would you mind looking over #81 if you get a chance? It has the kind of changes you’re looking for and I’m already using it in production, but it’s a pretty large change and could use some more eyes. |
Again, the issue is not the current API, it's that the API does not do what is is supposed to do under the hood. Yes, the right parser error events need to be listened, caught, and then thrown out of the middleware: #77. I just don't have days and weeks up my sleeve right now to work on it, I am spread super very thin right now. @mike-marcacci we could split out the tests in #81, which make up about 2/3rds of the diff, into a fast-tracked PR. The tests could be merged a lot quicker and might be helpful if others take a crack at solutions. |
@mike-marcacci Took a peek at your PR. There are obviously a lot of changes going on there that are really out of my capacity to fully follow or even grasp without going deep - but they seem ok to me and, again, in line with what we have been proposing in this thread. However, your pattern seems to match what @jaydenseric mentioned should not be part of the middleware's API - namely, passing an ad-hoc error handler as an option. Am I missing something here? While I, with my limited knowledge, don't see any other way forward, is this the approach we should be pursuing? Also, @jaydenseric, about your comment on handling errors outside the resolver: attaching a global handler to a Koa app won't cut it. Unhandled errors emitted from streams don't end up there and even if we manage to make it so, global handlers most probably won't have access to I/O resources that should be cleaned/closed - so its usefulness is kinda limited for the scenario we are discussing here. @mike-marcacci better summed up the idea by saying:
|
I seem to be running into this, but have been unable to reproduce it locally, using either or both the Chrome Dev Tools network throttling nor Mac OS's XCode "Network Link Conditioner" tool using the "Very Bad Network" setting. From what I can see in the source, there are calls to an
This is under |
@twelve17 are you handling errors on the stream created with The scenario discussed in this issue is long fixed, and extremely thoroughly tested. |
@mike-marcacci I am not! I had perused the other github issues related to this topic, and concur that it's one that has been discussed at length and that the issue was probably on my side, I just couldn't pinpoint, based on the exception, where I should be looking out for errors. Hopefully this will cover it. Thanks for your prompt reply. |
No problem, happy to help! |
Hmm, it appears Koa has a default error function that is associated with the error listener by default. I've tried to crash Koa by way of throwing and/or emitting errors without success, but I know that error handling is a big messy topic that can consist of many unique use cases. It's possible this may be an issue with koa's own error handling, but will need to dig deeper |
@mike-marcacci I'm doing some more debugging and am able to replicate the error locally, so this is a bit of progress. If I may, could I ask a really dumb question about this bit of handling? https://github.com/jaydenseric/graphql-upload/blob/master/src/processRequest.mjs#L153
If the request disconnected, does it make sense to try and send back a HTTP response here? |
Hi @twelve17, indeed a response to the client will not be received, which is fine. We still need to exit out of the running behavior and destroy the streams to avoid leaking resources. I still suspect you just haven't attached an error handler to the stream in your resolver, as koa has no way of "catching" an error of this sort. This is the kind of thing you would expect to write any time you create a stream or other event emitter: const stream = createReadStream();
stream.on("error", error => {
console.log("Here is the error in the upload stream:", error);
// cleanup code goes here...
}); |
@mike-marcacci I was handling errors on the graphql upload stream, but failed to do so on the other stream which was sending the data to the backend! So, thanks again for your help in steering me in the right direction. I owe you a 🍺! |
does anyone have news about this bug? |
I noticed that when a request ends, if at least one of the uploaded file streams was not consumed, an error is emitted. I've found out the hard way, that if you don't subscribe to the
'error'
event on the readable stream, the emission halts the node process and brings down the entire server.FileStreamDisconnectUploadError: Request disconnected during file upload stream parsing. at IncomingMessage.request.on (/dev/project/node_modules/apollo-upload-server/lib/middleware.js:151:15) at IncomingMessage.emit (events.js:182:13) at resOnFinish (_http_server.js:564:7) at ServerResponse.emit (events.js:187:15) at onFinish (_http_outgoing.js:683:10) at process._tickCallback (internal/process/next_tick.js:61:11) Emitted 'error' event at: at IncomingMessage.request.on (/dev/project/node_modules/apollo-upload-server/lib/middleware.js:149:32) at IncomingMessage.emit (events.js:182:13) [... lines matching original stack trace ...] at process._tickCallback (internal/process/next_tick.js:61:11)
This has the nasty side-effect of potentially allowing any GraphQL client to send a file under an unexpected field and tear down your server. I realize this after tracing a bug to the UI where the
file: Upload
field in the mutation was inadvertently being sent asattachment
. The API server was dying, without any chance of recovering or catching the error.You can try this using the
apollo-upload-examples
. Just change the expectedfile
field in thesingleUpload
mutation to any other name on the UI and run the upload.Is there any way of preventing this kind of behavior?
The text was updated successfully, but these errors were encountered: