-
Notifications
You must be signed in to change notification settings - Fork 374
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
createWriteStream({resumable:false}) causes error to be swallowed and program to hang #312
Comments
I ran into the same issue and spent quite a while trying to figure out what was happening. In my case, the contentType was being set to an invalid value, which was causing the error. The data is being fetched from a server and uploaded to datastore and the origin server is setting an invalid Content-Type. My workaround was simply removing "resumable: false" and double checking if the Content-Type was right. Is it possible to raise the priority for this? IMO it's likely that others may run into this issue and see their servers just hang, and it's quite tricky to find the source of the issue. Here's a code to replicated the error with invalid Content-Type. const fs = require('fs');
const config = require('./config/config');
const cloudStorage = require('@google-cloud/storage');
const CLOUD_BUCKET = config.get('CLOUD_BUCKET');
const storage = cloudStorage({
projectId: config.get('GCLOUD_PROJECT')
});
const bucket = storage.bucket(CLOUD_BUCKET);
const file = bucket.file('test.png');
const readStream = fs.createReadStream('./test-image.png');
const metadata = {
contentType: 'PNG' // Invalid Content-Type
};
const writeStream = file.createWriteStream({
metadata: metadata,
resumable: false
});
writeStream.on('error', err => {
console.log(err.message);
});
writeStream.on('finish', () => {
console.log('finish');
});
readStream.pipe(writeStream); |
I took a look at file.js and I think there's a possible fix. I think the issue seems to be tat that the stream to which the error is being emitted to is different from the one exposed to the user (eg: where stream.on('error', ...) is being called. const fileWriteStream = duplexify(); But the stream that is being returned to the user is const stream = streamEvents(
pumpify([
gzip ? zlib.createGzip() : through(),
validateStream,
fileWriteStream,
])
); I noticed that a little below the code, event emitted to fileWriteStream.on('response', stream.emit.bind(stream, 'response')); I tested out doing the same thing for the error event, and it helps in my case (the code posted previously works): fileWriteStream.on('error', stream.emit.bind(stream, 'error')); Are there other clean-ups that should be done instead of simply forwarding the event? |
ping @stephenplusplus |
@andreban thank you for that investigation. It didn't resolve @giltayar's issue for me, unfortunately, and it seems unnecessary as pumpify will destroy the whole "pumpified" stream if any of them emit an error. What I see when I run the original script is: $ node .
#### Run # 1
..........
#### Run # 2
{ Error: ESOCKETTIMEDOUT
at ClientRequest.<anonymous> (/Users/stephen/dev/play/gissue-312/node_modules/request/request.js:816:19)
at Object.onceWrapper (events.js:273:13)
at ClientRequest.emit (events.js:182:13)
at TLSSocket.emitRequestTimeout (_http_client.js:661:40)
at Object.onceWrapper (events.js:273:13)
at TLSSocket.emit (events.js:182:13)
at TLSSocket.Socket._onTimeout (net.js:449:8)
at ontimeout (timers.js:425:11)
at tryOnTimeout (timers.js:289:5)
at listOnTimeout (timers.js:252:5) code: 'ESOCKETTIMEDOUT', connect: false } A long time goes by before the timeout (2 or 3 minutes), but the error that is coming from the request library is being reported. So, I'm not sure why there is no error, but also no success. I'll keep looking. I just thought I'd share where I'm at so far. |
Think I found it: When this library tries to retry the request because of a 429 or any other error, the stream is already consumed and there's no data available to write to the socket. Then the socket times out expecting data. Good job finding a way to consistently reproduce the error. I think this is the same issue plaguing everyone in #27 (which we hit on a daily basis). For the record, the new error since the "request" module was replaced by "teeny-request":
The issue can be reduced to the following, bypassing a lot of the storage module's stream complexity: const {Storage} = require("."); // nodejs-storage
const {StringStream} = require('@rauschma/stringio')
const client = new Storage({projectId: "..."});
for (let i = 0; i < 30; i++) {
console.log(">>");
const body = new StringStream("abc");
client.request({
uri: "https://www.googleapis.com/upload/storage/v1/b/zb-dev/o",
method: "POST",
qs: {
uploadType: "multipart",
name: "foo/bar"
},
multipart: [
{"Content-Type": "application/json", body: '{"contentType":"text/plain"}'},
{"Content-Type": "text/plain", body}
]
}, (err, body, res) => {
console.log(err, body && body.id, res && res.statusCode)
});
}
// ~10x 200 responses, then times out Sequence of events:
These retries appear to happen twice after 60-second timeouts, before finally timing out entirely. This seems like a fundamental flaw with using streams as a datasource when the sink might require a retry, and I have no great ideas for how to fix it. Brainstorming:
|
Awesome, thank you for this research! 🙏
Yes, sadly. Here's a related issue in the retry-request library about retrying POST requests: googleapis/retry-request#3 (comment). I'm working on making this a single, un-retried request. I'll link you when I have a PR for review 👍 |
PR sent! googleapis/nodejs-common#268 |
Looks good to me! Since that changes the default though, does the number of retries need to be changed anywhere else? |
@JustinBeckwith @stephenplusplus just want to make sure my question above doesn't get lost -- it looks like that PR changed the default number of retries, so do non-zero retry values need to be set in this and other modules (for non-streaming requests obviously)? |
The PR didn't change the default for all methods, only |
Environment details
@google-cloud/storage
version: 1.7.0Steps to reproduce
npm install @google-cloud/storage @rauschma/stringio
)rateLimitExceeded
), but instead the code never finishes. This is the problem. The program should fail, because we're putting the same content in the same path too many times. (If you always put the text in random paths then everything works without a 429.)resumable: false
and run it againCode:
So
{resumable: false}
is causing the program to hang, I'm guessing because it's not reporting the error on the stream.The text was updated successfully, but these errors were encountered: