{"record":{"id":"b2ec18234cb979c4","repo":"nginx/nginx","slug":"ngx-log-crit-b2ec18","errorCode":"NGX_LOG_CRIT","errorMessage":"<ngx_read_file_n> read only %z of %z from \\\"%s\\\"","messagePattern":"<ngx_read_file_n> read only %z of %z from \\\\\"(.+?)\\\\\"","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/http/modules/ngx_http_mp4_module.c","lineNumber":1099,"sourceCode":"\n    if (mp4->buffer == NULL) {\n        mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size);\n        if (mp4->buffer == NULL) {\n            return NGX_ERROR;\n        }\n\n        mp4->buffer_start = mp4->buffer;\n    }\n\n    n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size,\n                      mp4->offset);\n\n    if (n == NGX_ERROR) {\n        return NGX_ERROR;\n    }\n\n    if ((size_t) n != mp4->buffer_size) {\n        ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0,\n                      ngx_read_file_n \" read only %z of %z from \\\"%s\\\"\",\n                      n, mp4->buffer_size, mp4->file.name.data);\n        return NGX_ERROR;\n    }\n\n    mp4->buffer_pos = mp4->buffer_start;\n    mp4->buffer_end = mp4->buffer_start + mp4->buffer_size;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)\n{\n    u_char     *ftyp_atom;\n    size_t      atom_size;\n    ngx_buf_t  *atom;","sourceCodeStart":1081,"sourceCodeEnd":1117,"githubUrl":"https://github.com/nginx/nginx/blob/3f6f7824d4e2eb1ac37dec76683d525ac0ff521c/src/http/modules/ngx_http_mp4_module.c#L1081-L1117","documentation":"After the earlier stat/open accounted for buffer_size bytes, ngx_read_file returned fewer. This CRIT-level log signals the file shrank underneath the running request (rewritten or truncated in place) or a serious storage problem; the request fails with 500.","triggerScenarios":"The file is replaced with a shorter one between the open in the content handler and this read: in-place rewrites by deploy scripts, logrotate-style truncation, or concurrent writers on shared storage.","commonSituations":"Deployment scripts overwriting live videos with cp; two writers publishing to the same path; failing disks returning short reads.","solutions":["Stop modifying served files in place: write a temp file and mv (rename) it into the final name.","After an incident, stat the file and compare with the expected size; restore if damaged.","Stage writes in a separate directory and swap via rename or symlink.","If sizes are stable but short reads persist, investigate storage health."],"exampleFix":"# before: in-place rewrite, readers can see a shrunken file mid-request\ncp new_video.mp4 /data/videos/video.mp4\n\n# after: atomic publish — rename() never exposes a partial file\ncp new_video.mp4 /data/videos/.video.mp4.tmp && mv /data/videos/.video.mp4.tmp /data/videos/video.mp4","handlingStrategy":"retry","validationCode":"# stability probe: a file changing size under requests is being rewritten in place\nts=$(stat -c %s /data/videos/v.mp4); sleep 5\n[ \"$ts\" = \"$(stat -c %s /data/videos/v.mp4)\" ] && echo stable || echo 'file changing under readers!'","typeGuard":null,"tryCatchPattern":"# client-side: retry once after a short delay; deploys finish and the read succeeds\nasync function fetchVideo(url) {\n  for (let attempt = 0; attempt < 2; attempt++) {\n    const res = await fetch(url);\n    if (res.ok) return res;\n    await new Promise(r => setTimeout(r, 500));\n  }\n  throw new Error('video unavailable');\n}","preventionTips":["Never overwrite served files in place; publish via write-temp-then-rename.","Stage new versions in a new path or filename and switch the link atomically.","Correlate CRIT short-read logs with deploy times to confirm the race source."],"tags":["nginx","mp4","file-io","race-condition","short-read"],"backgroundTag":"short-read","analyzedSha":"3f6f7824d4e2eb1ac37dec76683d525ac0ff521c","analyzedAt":"2026-08-22T03:09:46.447Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}