前一篇在開發 Conjure Piglet Client 時,提到了第三方函式庫 WebSocket 。我一開始還一度天真地以為,我只要找一些 Neovim plugin 或是 Lua library 裝一裝就可以完成了,真的開始動手之後,才發現這比我最初想象的困難多了。
在嘗試了幾個選項之後,最接近我的需求的選項是 lua-websockets 這個 library ,其它的選項有一些可能只有實作 websocket client 又或是並非純 Lua 的實作,由於希望將來這個 conjure client 不會太難安裝,我決定使用 Luarocks 安裝的 library 就是極限了。
為了測試方便,我還安裝了一個 websocat ,這樣子我可以先專注在測試 websocket ,而不需要 websocket 與 CBOR 一起測。
不久,我成功地用 lua-websockets 造出了一個 websocket echo server 。然後,第一個挑戰就出現了:「儘管用 websocat 做的 client 端,可以順利連上在 Neovim 裡執行的 websocket server ,但是當 client 切斷連線之後,Neovim 卻會整個凍結。」
這是怎麼回事呢?問題出在 Neovim 使用 lua-websockets 本來就不太合理。lua-websockets 函式庫提供了兩種非同步的機制:lua-ev
或是 copas
(coroutine) ,然而,無論是哪一個非同步機制,它的底層都不是 Neovim 的 vim.uv
。換言之,前述的作法等於是使用了兩個事件迴圈 (一個來自 Neovim,另一個來自 lua-websockets) ,自然很容易造成上述的凍結。
了解凍結的原因之後,合理的解法也很明顯了,應該要讓事件迴圈只有一個。於是,我決定修改 lua-websockets 的底層,讓它依賴於 Neovim 的 vim.uv
。簡單地來說,我決定要移植 lua-websockets 到 vim.uv
上。
在閱讀了 lua-websockets 的原始碼之後,我發現移植 (porting) 的重點,應該放在 src/websocket/server_ev.lua
即可。
這部分的移植,我用 LLM 來做。前後試了兩個 prompts,總算完成了可用的 websocket server.
將 src/websocket/server_ev.lua 重寫,使其能夠在 Neovim 中執行。 注意:原始實作依賴四個函式庫:
ev
、loop
、websocket.ev_common
、socket
。請移除對這些函式庫的依賴,改為使用vim.uv
。 …
Prompt 1 產生的程式碼大致可用,但是其中一個函數 message_io
算是嚴重的幻覺,而這個的幻覺滿合理的,因為我沒有注意到 websocket.ev_common
其實也是 server_ev.lua
的主要部分,而非外部依賴。
於是,我用了第二個 prompt 來修正這個問題。
考慮 src/websocket/ev_common.lua 裡的
message_io
,它依賴於lua ev
的loop
。請重構message_io
,並將該message_io
裡的sock
改成vim.uv
裡的uv.new_tcp()
的傳回值,也就是說,這個sock
它有read_start
,write
等函式可以呼叫。 ...
在兩個 Prompt 之後,我再做一些簡單的人工除錯,WebSocket 連線就打通了。
某種程度來講,這回解的問題也還是跟之前 day20, day21 一樣是移植問題。大的移植問題裡,又有小的移植問題。
這個移植問題,由於我已經確定了它的依賴項目了,所以 LLM 的 prompt 就可以順利處理。另一方面,如果沒有用人工來將依賴關系加釐清,LLM 成功的機率就非常低了。