feat(ncm §21): 边下边播阶段1+2——stream_src流式源+decoder双路open_cb+下载线程预缓冲/stall联动+P2 HTTP空闲超时+cache fd写者+stream_sim全矩阵PCM金标准(0真实请求,未部署)
This commit is contained in:
1 parent
dbee37e1d6
commit
9e25db77cc
18 files changed
+1335
-136
No files matched your search
@@ -4,6 +4,8 @@
|
||||
#include "ncm_store.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -95,7 +97,7 @@ int ncm_cache_lookup(const ncm_cache_t *c, const char *id, const char *ext,
|
||||
struct ncm_cache_writer {
|
||||
char final_path[NCM_CACHE_DIR_MAX + 64];
|
||||
char tmp_path[NCM_CACHE_DIR_MAX + 80];
|
||||
FILE *fp;
|
||||
int fd; /* 写 fd(write(2) 直写,§21:不走 stdio 缓冲,字节对读端即时可见) */
|
||||
uint64_t nwritten;
|
||||
int failed;
|
||||
};
|
||||
@@ -105,23 +107,39 @@ int ncm_cache_open(const ncm_cache_t *c, const char *id, const char *ext,
|
||||
if (!c || !id || !w_out) return NCM_ERR_ARG;
|
||||
ncm_cache_writer_t *w = (ncm_cache_writer_t *)calloc(1, sizeof *w);
|
||||
if (!w) return NCM_ERR_OOM;
|
||||
w->fd = -1;
|
||||
if (ncm_cache_path(c, id, ext, w->final_path, sizeof w->final_path) != 0) {
|
||||
free(w); return NCM_ERR_FULL;
|
||||
}
|
||||
snprintf(w->tmp_path, sizeof w->tmp_path, "%s.tmp", w->final_path);
|
||||
w->fp = fopen(w->tmp_path, "wb");
|
||||
if (!w->fp) { free(w); return NCM_ERR_IO; }
|
||||
w->fd = open(w->tmp_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (w->fd < 0) { free(w); return NCM_ERR_IO; }
|
||||
*w_out = w;
|
||||
return NCM_OK;
|
||||
}
|
||||
|
||||
/* 写端用 write(2) 直写(§21):旧版 fwrite 有 4-8KB stdio 缓冲,字节对边下边播的
|
||||
* pread 读端不可见;直写后每块落盘即推进可读边界。无用户态缓冲,fsync 只在 commit 一次。 */
|
||||
int ncm_cache_write(ncm_cache_writer_t *w, const unsigned char *data, size_t len) {
|
||||
if (!w || !w->fp || w->failed) return NCM_ERR_ARG;
|
||||
if (fwrite(data, 1, len, w->fp) != len) { w->failed = 1; return NCM_ERR_IO; }
|
||||
if (!w || w->fd < 0 || w->failed) return NCM_ERR_ARG;
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
ssize_t n = write(w->fd, data + off, len - off);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
w->failed = 1; return NCM_ERR_IO;
|
||||
}
|
||||
off += (size_t)n;
|
||||
}
|
||||
w->nwritten += len;
|
||||
return NCM_OK;
|
||||
}
|
||||
|
||||
/* 写者 .tmp 路径(边下边播:解码侧用它开只读 fd 建 stream_src;只读,勿改)。 */
|
||||
const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w) {
|
||||
return w ? w->tmp_path : "";
|
||||
}
|
||||
|
||||
static int cmp_mtime_asc(const void *a, const void *b) {
|
||||
const entry_t *x = (const entry_t *)a, *y = (const entry_t *)b;
|
||||
if (x->mtime < y->mtime) return -1;
|
||||
@@ -169,15 +187,14 @@ int ncm_cache_evict(const ncm_cache_t *c) {
|
||||
int ncm_cache_commit(ncm_cache_writer_t *w) {
|
||||
if (!w) return NCM_ERR_ARG;
|
||||
int rc = NCM_OK;
|
||||
if (w->failed || !w->fp) rc = NCM_ERR_IO;
|
||||
else if (fflush(w->fp) != 0) rc = NCM_ERR_IO;
|
||||
else if (fsync(fileno(w->fp)) != 0) rc = NCM_ERR_IO;
|
||||
else if (fclose(w->fp) != 0) rc = NCM_ERR_IO;
|
||||
if (w->failed || w->fd < 0) rc = NCM_ERR_IO;
|
||||
else if (fsync(w->fd) != 0) rc = NCM_ERR_IO;
|
||||
else if (close(w->fd) != 0) { w->fd = -1; rc = NCM_ERR_IO; }
|
||||
else {
|
||||
w->fp = NULL;
|
||||
w->fd = -1;
|
||||
if (rename(w->tmp_path, w->final_path) != 0) { unlink(w->tmp_path); rc = NCM_ERR_IO; }
|
||||
}
|
||||
if (w->fp) { fclose(w->fp); w->fp = NULL; }
|
||||
if (w->fd >= 0) { close(w->fd); w->fd = -1; }
|
||||
if (rc == NCM_OK) {
|
||||
/* 刷新 mtime(= 最近访问)*/
|
||||
utime(w->final_path, NULL);
|
||||
@@ -188,7 +205,7 @@ int ncm_cache_commit(ncm_cache_writer_t *w) {
|
||||
|
||||
int ncm_cache_abort(ncm_cache_writer_t *w) {
|
||||
if (!w) return NCM_ERR_ARG;
|
||||
if (w->fp) { fclose(w->fp); w->fp = NULL; }
|
||||
if (w->fd >= 0) { close(w->fd); w->fd = -1; }
|
||||
unlink(w->tmp_path);
|
||||
free(w);
|
||||
return NCM_OK;
|
||||
|
||||
@@ -43,13 +43,16 @@ int ncm_cache_path(const ncm_cache_t *c, const char *id, const char *ext,
|
||||
int ncm_cache_lookup(const ncm_cache_t *c, const char *id, const char *ext,
|
||||
char *out, size_t out_sz);
|
||||
|
||||
/* 流式写入句柄:open 建 temp;write 累积;commit 落盘+刷新LRU+触发淘汰;abort 丢弃。 */
|
||||
/* 流式写入句柄:open 建 temp;write 累积(write(2) 直写无 stdio 缓冲,§21 边下边播
|
||||
* 读端即时可见);commit 落盘+刷新LRU+触发淘汰;abort 丢弃。 */
|
||||
typedef struct ncm_cache_writer ncm_cache_writer_t;
|
||||
int ncm_cache_open(const ncm_cache_t *c, const char *id, const char *ext,
|
||||
ncm_cache_writer_t **w);
|
||||
int ncm_cache_write(ncm_cache_writer_t *w, const unsigned char *data, size_t len);
|
||||
int ncm_cache_commit(ncm_cache_writer_t *w); /* win; 返回 0=成功 */
|
||||
int ncm_cache_abort(ncm_cache_writer_t *w);
|
||||
/* 写者 .tmp 路径(只读):边下边播解码侧用它开只读 fd 建 stream_src。 */
|
||||
const char *ncm_cache_writer_tmp_path(const ncm_cache_writer_t *w);
|
||||
|
||||
/* 当前缓存总字节(扫描目录) */
|
||||
uint64_t ncm_cache_total_bytes(const ncm_cache_t *c);
|
||||
|
||||
@@ -328,8 +328,15 @@ static int http_run(const ncm_http_req_t *req, sink_t *sink, ncm_http_resp_t *re
|
||||
if (parse_url(req->url, &url) != 0) return NCM_ERR_ARG;
|
||||
if (strcmp(url.scheme, "https") != 0 && strcmp(url.scheme, "http") != 0) return NCM_ERR_ARG;
|
||||
|
||||
/* 超时语义(P2 改造,REPORT §21):连接/握手/请求发送/响应头 保留绝对总上限 15s;
|
||||
* 响应体阶段改**空闲超时**——每成功读到字节即顺延,15s 无字节才判超时。
|
||||
* 旧实现 timeout_ms=30000 是全程绝对 deadline,整首下载超 30s 必 abort(慢网暗雷);
|
||||
* 边下边播下载持续整曲时长,必须先改为空闲语义。 */
|
||||
uint32_t tmo = req->timeout_ms ? req->timeout_ms : 15000;
|
||||
if (tmo > 15000) tmo = 15000; /* 绝对段统一钳到 15s */
|
||||
int64_t deadline = ncm_monotonic_ms() + (int64_t)tmo; /* 截止时刻用单调钟,避免 NTP 校时跳变 */
|
||||
int64_t body_deadline = 0; /* hdr_done 后启用:每收字节顺延 15s */
|
||||
#define NCM_HTTP_BODY_IDLE_MS 15000
|
||||
|
||||
|
||||
mbedtls_net_context net;
|
||||
@@ -467,10 +474,13 @@ static int http_run(const ncm_http_req_t *req, sink_t *sink, ncm_http_resp_t *re
|
||||
if (body_feed(&bst, tmp, n, sink) < 0) { ret = NCM_ERR_JSON; goto done; }
|
||||
}
|
||||
if (sink->overflow || bst.st == BS_DONE) break;
|
||||
if (hdr_done) body_deadline = ncm_monotonic_ms() + NCM_HTTP_BODY_IDLE_MS; /* 有字节→顺延(P2 空闲超时) */
|
||||
} else if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
|
||||
if ((ret = wait_io(&net, MBEDTLS_NET_POLL_READ, deadline)) != 0) { if (ret==MBEDTLS_ERR_SSL_TIMEOUT) ret=NCM_ERR_TIMEOUT; goto done; }
|
||||
int64_t dl = hdr_done ? body_deadline : deadline; /* 响应体=空闲超时;此前=绝对上限 */
|
||||
if ((ret = wait_io(&net, MBEDTLS_NET_POLL_READ, dl)) != 0) { if (ret==MBEDTLS_ERR_SSL_TIMEOUT) ret=NCM_ERR_TIMEOUT; goto done; }
|
||||
} else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
if ((ret = wait_io(&net, MBEDTLS_NET_POLL_WRITE, deadline)) != 0) goto done;
|
||||
int64_t dl = hdr_done ? body_deadline : deadline;
|
||||
if ((ret = wait_io(&net, MBEDTLS_NET_POLL_WRITE, dl)) != 0) goto done;
|
||||
} else if (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
|
||||
break; /* 对端关闭 */
|
||||
} else {
|
||||
|
||||
@@ -36,7 +36,8 @@ typedef struct ncm_http_req {
|
||||
const char *body; /* 表单体,可为 NULL */
|
||||
size_t body_len; /* 0 表示 strlen(body) */
|
||||
const char *extra_hdrs; /* 附加头(完整 CRLF 行),可为 NULL */
|
||||
uint32_t timeout_ms; /* 整体超时(ms);0=默认 15000 */
|
||||
uint32_t timeout_ms; /* 连接/握手/请求/响应头的绝对总上限(ms,>15s 钳到 15s);0=默认 15000。
|
||||
* 响应体阶段不受此限:改空闲超时,每收到字节顺延,15s 无字节判超时(P2,§21)。 */
|
||||
int verify_ca; /* 1=校验CA,0=跳过(仅调试) */
|
||||
const char *ca_path; /* 指定 CA bundle 路径;NULL=系统默认 */
|
||||
} ncm_http_req_t;
|
||||
|
||||
Reference in new issue
Block a user