libuv裡的幾個缺陷,libuv裡缺陷
libuv是node.js使用的基礎庫,主要包括主迴圈,檔案和網路介面。雖然libuv是為node.js而生的,但它本身是一個獨立的庫,加上使用簡單方便,所以在node.js之外也有不少人使用。最近整合libuv到V8裡時發現幾個問題:
1.uv_fs相關函數無法傳回呼函數需要的上下文資訊(如read的buffer),只能通過全域變數來儲存資料(官方例子都是用的全域變數)。uv_fs相關函數都可以提供一個回呼函數,如果回呼函數不為空白,當前調用自動變成一個非同步呼叫,在操作完成時調用提供的回呼函數。一般來說,回呼函數都需要一個變數來作為它的上下文(提供參數或儲存結果),檔案操作相關函數的上下文是uv_fs_t結構,但裡面無法儲存調用者提供的額外資訊。
uv_fs_t open_req;uv_fs_t read_req;uv_fs_t write_req;static char buffer[1024];static uv_buf_t iov;...void on_read(uv_fs_t *req) { if (req->result < 0) { fprintf(stderr, "Read error: %s\n", uv_strerror(req->result)); } else if (req->result == 0) { uv_fs_t close_req; // synchronous uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL); } else if (req->result > 0) { iov.len = req->result; uv_fs_write(uv_default_loop(), &write_req, 1, &iov, 1, -1, on_write); }}void on_open(uv_fs_t *req) { // The request passed to the callback is the same as the one the call setup // function was passed. assert(req == &open_req); if (req->result >= 0) { iov = uv_buf_init(buffer, sizeof(buffer)); uv_fs_read(uv_default_loop(), &read_req, req->result, &iov, 1, -1, on_read); } else { fprintf(stderr, "error opening file: %s\n", uv_strerror((int)req->result)); }}...uv_fs_open(uv_default_loop(), &open_req, argv[1], O_RDONLY, 0, on_open);
我開始以為data成員是儲存使用者資料的地方,通過data把buffer傳過去,但是data總是被清空了,看裡面的代碼才知道data是fs內部的使用的。上面官方例子都是通過全域變數傳過去的,真是太變態了!
2.其它線程不能訪問預設主迴圈。最近讓cantk-runtime-v8裡支援Touch/Key事件遇到這個問題:Touch/Key事件是UI線程裡的,而V8是在GLSurfaceView的Render線程裡的,直接通過JNI調用V8裡的JS會導致程式崩潰,所以我想通過uv_idle來序列化,但結果是程式仍然崩潰。記得glib loop裡idle是允許多線程訪問的,我在設計FTK的主迴圈時也通過一個pipe來序列化多線程訪問主迴圈的,呵呵,我以為所有的loop都應該支援多線程,一看代碼才知道,它並沒有加鎖也沒有用pipe來序列化:
int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) { \ if (uv__is_active(handle)) return 0; \ if (cb == NULL) return -EINVAL; \ QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue); \ handle->name##_cb = cb; \ uv__handle_start(handle); \ return 0; \ }
3.uv_async無法傳遞資料。用uv_idle不行,我決定用uv_async。這次倒是不崩潰了,事件好像也收到了,但遊戲裡的反應卻有些怪異,仔細分析LOG資訊,發現touchmove和touchend收到了,但是沒有收到touchstart。明明uv_async_send都執行了,為什麼主迴圈卻沒有處理這個事件呢?繼續看代碼:
void uv__async_send(struct uv__async* wa) { const void* buf; ssize_t len; int fd; int r; buf = ""; len = 1; fd = wa->wfd;#if defined(__linux__) if (fd == -1) { static const uint64_t val = 1; buf = &val; len = sizeof(val); fd = wa->io_watcher.fd; /* eventfd */ }#endif do r = write(fd, buf, len); while (r == -1 && errno == EINTR); if (r == len) return; if (r == -1) if (errno == EAGAIN || errno == EWOULDBLOCK) return; abort();}
async_send是同步執行的,從上面的代碼看不出什麼問題,再看接受部分:
static void uv__async_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) { struct uv__async* wa; char buf[1024]; unsigned n; ssize_t r; n = 0; for (;;) { r = read(w->fd, buf, sizeof(buf)); if (r > 0) n += r; if (r == sizeof(buf)) continue; if (r != -1) break; if (errno == EAGAIN || errno == EWOULDBLOCK) break; if (errno == EINTR) continue; abort(); } wa = container_of(w, struct uv__async, io_watcher);#if defined(__linux__) if (wa->wfd == -1) { uint64_t val; assert(n == sizeof(val)); memcpy(&val, buf, sizeof(val)); /* Avoid alignment issues. */ wa->cb(loop, wa, val); return; }#endif wa->cb(loop, wa, n);}
全部資料讀取完了,只是在最後調了一次回調。更鬱悶的是我對async的理解是錯的:uv_async_t裡的data成員並不能用來傳遞資料,它是在兩個線程中無保護的情況下共用的。下面的代碼是官方提供的樣本,這種方法在低速通訊時沒問題,速度一快後面的資料自動覆蓋前面的資料,所以touchstart被touchmove覆蓋而丟失。
void fake_download(uv_work_t *req) { int size = *((int*) req->data); int downloaded = 0; double percentage; while (downloaded < size) { percentage = downloaded*100.0/size; async.data = (void*) &percentage; uv_async_send(&async); sleep(1); downloaded += (200+random())%1000; // can only download max 1000bytes/sec, // but at least a 200; }}
libuv以上這些問題在node.js恰恰不是問題,但獨立使用libuv時一定要小心了。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。