92144


### 質問概要

Win32APIで実現している以下の処理を、Javaで実現する方法についてご存じでしたら教えて頂けないでしょうか？  
**「ファイルオープンをデータの読み取りおよび書き込み用のシステム キャッシュなしで行う」**

調査をしていますが、現在のところ手段が見当たりません。  
また、sun.nio.fs.WindowsNativeDispatcher
で実現可能との情報も得ましたが、"sun"以下のパッケージは業務の都合上使用できない為、断念しました。

### 参考にしたサイト

  * CreateFile の引数 flagsAndAttributes に、FILE_FLAG_NO_BUFFERING を指定してファイルオープン  
<https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-
createfilew>

  * システム キャッシュ  
<https://learn.microsoft.com/ja-jp/windows/win32/fileio/file-caching>

### NO_BUFFERINGが必要な理由

この質問内容にアドバイス頂きありがとうございます。NO_BUFFERING が必要な理由について説明を追記します。

例として、1つのファイルに対し、ほぼ同時のタイミングでA,Bの2者が以下の作業をしたとします。  
①Aがファイルを参照  
②Bも上記ファイルを参照  
③Aが上記ファイルを更新  
④Bが上記ファイルを再度参照

④の参照時において、Aが更新した内容が反映されていれば特に問題ないです。  
しかし、万が一、実ファイルの内容が未反映のディスクキャッシュを参照していた場合、Bは、Aの更新内容が反映されていない状態のファイルを参照することになってしまう（=②の状態で参照してしまう）ことを懸念しています。

その為、NO_BUFFERING で参照できれば上記懸念が解消されると考えています。  
尚、排他制御による解決も考えられる可能性がありますが、業務の都合上排他制御の追加はできない状況となってしまっております。

### これまでの調査内容

Javaのjava.io. **FileInputStreamを使用してファイルオープンすると、システム キャッシュを使用してオープンする** ようです。  
※以下の通りソースを追った結果、CreateFileW flagsAndAttributes に対して、FILE_ATTRIBUTE_NORMAL
を指定してオープンしているように見える。

#### ■Java側の確認（JDK8側のソース抜粋）

  * FileInputSteram.java のファイルオープン、ファイル読み込み時  
<https://github.com/AdoptOpenJDK/openjdk-
jdk8u/blob/master/jdk/src/share/classes/java/io/FileInputStream.java>  
コンストラクタ ⇒ open⇒ open0 の順で呼ばれる。

[code]

    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }
    private void open(String name) throws FileNotFoundException {
        open0(name);
    }
    private native void open0(String name) throws FileNotFoundException;
    
[/code]

  * FileInputStream.c  
<https://github.com/AdoptOpenJDK/openjdk-
jdk8u/blob/master/jdk/src/share/native/java/io/FileInputStream.c>  
open0 ⇒ fileOpen の順に呼ばれる。

[code]

    JNIEXPORT void JNICALL Java_java_io_FileInputStream_open0(JNIEnv *env,jobject this, jstring path) {
        fileOpen(env, this, path, fis_fd, O_RDONLY); 
    }
    
[/code]

  * io_util_md.c  
<https://github.com/AdoptOpenJDK/openjdk-
jdk8u/blob/9a751dc19fae78ce58fb0eb176522070c992fb6f/jdk/src/windows/native/java/io/io_util_md.c>  
fileOpen ⇒ winFileHandleOpen の順に呼ばれる。

[code]

    void fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags) {
        FD h = winFileHandleOpen(env, path, flags);
        if (h >= 0) {
            SET_FD(this, h, fid);
        }
    }
    FD
    winFileHandleOpen(JNIEnv *env, jstring path, int flags) {
        const DWORD access =
            (flags & O_WRONLY) ?  GENERIC_WRITE :
            (flags & O_RDWR)   ? (GENERIC_READ | GENERIC_WRITE) :
            GENERIC_READ;
        const DWORD sharing =
            FILE_SHARE_READ | FILE_SHARE_WRITE;
        const DWORD disposition =
            /* Note: O_TRUNC overrides O_CREAT */
            (flags & O_TRUNC) ? CREATE_ALWAYS :
            (flags & O_CREAT) ? OPEN_ALWAYS   :
            OPEN_EXISTING;
        const DWORD  maybeWriteThrough =
            (flags & (O_SYNC | O_DSYNC)) ?
            FILE_FLAG_WRITE_THROUGH :
            FILE_ATTRIBUTE_NORMAL;
        const DWORD maybeDeleteOnClose =
            (flags & O_TEMPORARY) ?
            FILE_FLAG_DELETE_ON_CLOSE :
            FILE_ATTRIBUTE_NORMAL;
        const DWORD flagsAndAttributes = maybeWriteThrough | maybeDeleteOnClose;
        HANDLE h = NULL;
    
        WCHAR *pathbuf = pathToNTPath(env, path, JNI_TRUE);
        if (pathbuf == NULL) {
            /* Exception already pending */
            return -1;
        }
        h = CreateFileW(
            pathbuf,            /* Wide char path name */
            access,             /* Read and/or write permission */
            sharing,            /* File sharing flags */
            NULL,               /* Security attributes */
            disposition,        /* creation disposition */
            flagsAndAttributes, /* flags and attributes */
            NULL);
        free(pathbuf);
    
        if (h == INVALID_HANDLE_VALUE) {
            throwFileNotFoundException(env, path);
            return -1;
        }
        return (jlong) h;
    }
    
[/code]

以上、よろしくお願いいたします。


CC BY-SA 4.0
2022-11-10T03:04:38.500
92120
2022-11-11T23:32:02.843
2022-11-10T05:52:21.157
55309
55309
question
java ファイル入出力
Javaでファイルオープンをデータの読み取りおよび書き込み用のシステム キャッシュなしで行う方法について


`NO_BUFFERING` だけでは下記のようなシナリオは回避できないですので考えるだけ無駄かも

論理的には１つのデータ (まあ `class` を想定) が、たまたま大容量記憶装置のセクタ（あるいはクラスタ） `m` と `n`
にまたがって保存されているとき (`m` と `n` が離れているとき起きやすいが連続していても起きにくいだけで可能性は０ではない）

[code]

    スレッド１－－－－－－－－ m を書く－n を書く－－－－－－－－－－
    
    スレッド２－ m を読む－－－－－－－－－－－－－－　n を読む－－－
    　　ここで読んだ m は古い値　　　　　　　　　　ここで読んだ n は新しい値
    
[/code]

スレッド２で読んだ値は一部古く、一部新しい、つまり壊れている

まあ普通に 16bit CPU で 32bit 値を処理するのは atomic ではない 32bit CPU で 64bit 値を処理するのは atomic
ではないって奴と状況は全く同じ。なので原理原則通り「 atomic でない処理が重なる可能性があるなら排他が必要」ってことです。
`NO_BUFFERING` で再現しなくなったのではなく、確率が減っただけです。  
# たまたま今の Windows の内部実装だと確率０＝発生しないかもしれないが

なのできっちり排他することをお勧めしておきます。これを示しても「デッドロックするバグがとり切れなかったから排他は使用禁止」などという上司がいたら、オイラは開発技術力を疑ったり体制を疑ったり、するでしょうね


CC BY-SA 4.0
2022-11-10T23:48:40.297
92139
2022-11-10T23:48:40.297
8589
92120
answer


事実に基づいていないので、回答としては不適切な点ご容赦願いします。  
@774RR さんに修正を断られたので仕方なく書いている次第です。

# 回答

個人的には **JNIを使用しないと実現は不可能だと思います** 。  
それを含むライブラリが市販されているかどうかという意味であれば分かりません。

# 同期について

ディスクアクセスについての同期であれば、ファイルかページのロックだと思いますが、Javaに限定できてノードやプロセスが1つなら、ディスクアクセスよりはもう少し上の方で同期すべきな印象を持ちました(感想)。あとディスクキャッシュはOSやデバイスがするものであり、バッファリングはアプリ/ライブラリ側がするものです。OS以降で同期が問題になるケースがあるならファイルシステム固有の仕様か、OSやデバイスのバグのように思います。いずれにしても本来回答するなら、もう少し質問が具体的になってからですね。


CC BY-SA 4.0
2022-11-11T02:49:30.727
92144
2022-11-11T02:49:30.727
54957
92120
answer


ざっとしたソースを追ったところ、`newInputStream()`や`ewFileChannel()`等でファイルを開くときに[`com.sun.nio.file.ExtendedOpenOption.DIRECT`](https://github.com/openjdk/jdk/blob/jdk-17+35/src/jdk.unsupported/share/classes/com/sun/nio/file/ExtendedOpenOption.java#L52-L72)オプションを付ければ、[`Java_sun_nio_ch_FileDispatcherImpl_setDirect0()`](https://github.com/openjdk/jdk/blob/jdk-17%2B35/src/java.base/windows/native/libnio/ch/FileDispatcherImpl.c#L485-L513)が呼ばれて`FILE_FLAG_NO_BUFFERING`付きで再オープンされるようです。

注意点としては下記の通りです。

  * jdk.unsupported モジュールにあるため、JDK固有であり、また、サポートは受けられないと思われます。
  * Java 10から実装されたため、Java 8にはありません。
  * 日本語訳されたAPIドキュメントにはありませんので、ソースコード上のコメントを参考にしてください。

sun配下では無くcom.sun配下になりますので、「"sun"以下のパッケージは業務の都合上使用できない」は回避できるかと思います。ただ、com.sunも駄目というなら、実装元の`jdk.internal.misc.FileSystemOption`を無理矢理呼び出すという手もありますが、こちらは指定のモジュール以外には非公開のjdk.internal.micsパッケージにあり、イレギュラーな使い方になるかと思います。


CC BY-SA 4.0
2022-11-11T23:32:02.843
92172
2022-11-11T23:32:02.843
7347
92120
answer