遇到一個問題,.NET Core 的 File Watcher 機制(在檔案新增修改刪除時觸發 .NET 事件,參考)在 Docker 中可能失效。

註:File Watcher 可做到 Config 檔修改讀取生效、程式檔案異動時重新編譯,或是資料檔改變清除 Cache,非常實用。

以下是我重現問題的範例:

class Program
{
    private static PhysicalFileProvider _fileProvider =
new PhysicalFileProvider(Directory.GetCurrentDirectory());
    static void Main(string[] args)
    {
        Task.Factory.StartNew(() =>
        {
            Thread.Sleep(5000);
            Console.WriteLine("update quotes after 5 seconds");
            File.WriteAllText("quotes.txt", DateTime.Now.ToString());
        });
        WaitFileChange().GetAwaiter().GetResult();
        Console.WriteLine("done");
    }

    static async Task WaitFileChange()
    {
        IChangeToken token = _fileProvider.Watch("quotes.txt");
        var tcs = new TaskCompletionSource<object>();
        token.RegisterChangeCallback(state =>
        ((TaskCompletionSource<object>)state).TrySetResult(null), tcs);
        await tcs.Task.ConfigureAwait(false);
        Console.WriteLine("quotes.txt changed");
    }
}

程式的運作原理為使用 PhysicalFileProvider.Watch() 觀注 quotes.txt 檔案,以 IChangeToken.RegisterChangeCallback() 註冊檔案變更事件並利用 TaskCompletionSource 同步化,在檔案變更時印出 "quotes.txt changed"。在此同時,另開一條 Thread 在 5 秒後複寫 quotes.txt 檔案以觸發事件,故正常狀況應為,程式開始後等待五秒出現 "update quotes after 5 seconds",隨即印出 "quotes.txt changed",最後顯示 "done" 測試完成。

程式在 Windows 測試 OK,部署到 Linux 執行也沒問題,包進 Docker 裡跑結果也正確,但若使用 Docker Volume 將 /app/quotes.txt 對映到實體主機的 /home/jeffrey/dockers/fwt/quotes.txt 時,本機的 quotes.txt 檔案會被覆寫,但 RegisterChangeCallback() 事件不會被觸發。

.NET Core File Providers 文件 是有提到這點:

Some file systems, such as Docker containers and network shares, may not reliably send change notifications. Set the DOTNET_USE_POLLING_FILE_WATCHER environment variable to 1 or true to poll the file system for changes every four seconds (not configurable).

意思是這個機制遇到 Docker 檔案系統可能失效,必須設定 DOTNET_USE_POLLING_FILE_WATCHER 環境變數為 1 或 true,改用 4 秒一次的輪詢取代。實務上可在啟動 Docker 時透過 docker run ... -e DOTNET_USE_POLLING_FILE_WATCHER=1 ... 參數指定,更治本的解法則是在 Dockerfile 宣告 ENV 內嵌到容器裡:

FROM microsoft/dotnet:2.1-runtime
ENV DOTNET_USE_POLLING_FILE_WATCHER=1
WORKDIR /app
COPY ./publish ./
ENTRYPOINT ["dotnet", "FileWatcherTest.dll"]

貌似一行搞定,但我卻在這裡陷入泥坑,設好 DOTNET_USE_POLLING_FILE_WATCHER 卻不見無效,原以為可以輕鬆寫完的文章,反覆試了近兩多小時,將近午夜電腦都要變回南瓜仍然無解,讓我萬念俱灰了無生趣。

爬文時由一則 Github Issue 追到另一則被合併至 2.2 版的相關 PR,浮現靈感,查詢我安裝 Microsoft.Extensions.FileProviders.Physical 的 NuGet 套件版本為 2.1 穩定版,改裝 2.2 Preview 姑且一試。

喵的,就這麼成功了~

這枚 Microsoft.Extensions.FileProviders.Physical 程式庫升級 2.2 預覽才解掉的茶包,算是我第一次踩到的 .NET Core + Docker 地雷,不怕不怕,呵。

補充今天查到的Docker Image 版本補充,你知道 2.1-runtime-deps、2.1-runtime 有什麼不同? 什麼情況該用哪一種?

  • microsoft/dotnet:2.1.0-runtime-deps - use for deploying self-contained deployment apps
  • microsoft/dotnet:2.1.0-runtime - use for deploying .NET Core console apps
  • microsoft/dotnet:2.1.0-aspnetcore-runtime - use for deploying ASP.NET Core apps
  • microsoft/dotnet:2.1.300-sdk - use for building .NET Core (or ASP.NET Core apps)

Using an example to reproduce file watcher issue in Docker with volume mapping. Setting DOTNET_USE_POLLING_FILE_WATCHER=1 doesn't works with Microsoft.Extensions.FileProviders.Physical 2.1, upgrade to 2.2 preview is the workaround.


Comments

# by Mars

2.1 為 LTS 版本竟然沒有修正這問題也頗奇怪的 @@

# by Victor

不奇怪啊,現在都改走release date先決、hotfix style了。時間到了有什麼就推什麼,塞不進去的就等下一版....

Post a comment