Description
On Windows, I have an SMB share mounted as Z:
.
Using it as the target directory results in an error
func main() {
opts := badger.DefaultOptions
path := `Z:\db`
opts.Dir = path
opts.ValueDir = path
db, err := badger.Open(opts)
if err != nil {
log.Fatal(err)
}
db.Close()
}
badger 2019/01/28 23:02:09 INFO: All 0 tables opened in 0s
badger 2019/01/28 23:02:09 INFO: Replaying file id: 0 at offset: 0
badger 2019/01/28 23:02:09 INFO: Replay took: 0s
badger 2019/01/28 23:02:09 WARNING: While forcing compaction on level 0: Unable to fill tables
badger 2019/01/28 23:02:09 INFO: All 0 tables opened in 0s
2019/01/28 23:02:09 While syncing directory: Z:\bd.: sync Z:\bd: Incorrect function.
exit status 1
The source of the error seems to be returned from the FlushFileBuffers
syscall here:
https://github.com/golang/go/blob/66065c3115861c73b8804037a6d9d5986ffa9913/src/syscall/zsyscall_windows.go#L970
which is called as a result of bader.syncDir -> os.File.Fsync
While investigating this, I found it strange that this call does not return an error for local drives, but does for network drives.
MSDN states that the handle passed in should either be a handle to a file, or to a volume, but says nothing about directories.
https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-flushfilebuffers
And FlushFileBuffers is not listed as a function that accepts directory handles
https://docs.microsoft.com/en-us/windows/desktop/fileio/obtaining-a-handle-to-a-directory
I could be wrong but I believe this is an invalid/ineffectual argument to pass to this function on this platform. Or at the very least, not API defined, and thus not guaranteed to succeed.
So even for local disks where this currently returns without an error, files should probably be synced individually by their opened handles anyway.
And while this function accepts volumes, I don't think triggering a full disk sync is likely desirable.
In the SMB case you can't retrieve a handle to the volume through the same methods either, but syncing individual files appears to be valid. (it should trigger an SMB FLUSH
request which should force the transmit buffer to flush at least to the network).
For some additional context, (while not safe) commenting out err = f.Sync()
under syncDir()
allowed me to run an instance of go-ipfs
using badger as the datastore (hosted on the SMB share) without any obvious issues. Although testing was not extensive.
To avoid the possibility that this was a Go related issue, I probed around on my systems using a small C program. I listed the various results for the given arguments in the comments.
Test environment was W10 connecting to a remote share on a Solaris system, a remote share on a W10 system, and W10 connecting to a share it itself is hosting (local loop with full access).
All had the same output.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main(void) {
// (if exists) Does not fail but behaviour for this is not defined
//LPCWSTR target = L"C:\\Users\\Dominic Della Valle\\AppData\\Local\\Temp";
// (if exists) Flush fails with appropriate error value ERROR_INVALID_FUNCTION
//LPCWSTR target = L"Z:\\smb-test";
// Succeeds; triggers full disk/volume sync
//LPCWSTR target = L"C:";
// CreateFile fails with ERROR_ACCESS_DENIED; Returned even with full domain access on local domain
// (Windows usually returns ERROR_ACCESS_DENIED as a generic failure)
//LPCWSTR target = L"Z:";
// Flush would likely succeed if you could retrieve a handle to the network volume (is this possible?)
// (if exists) Succeeds on file target
LPCWSTR target = L"Z:\\smb-test\\file";
HANDLE remoteFile = CreateFileW(target, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (remoteFile == INVALID_HANDLE_VALUE) {
printf("Invalid handle: %lu", GetLastError());
return EXIT_FAILURE;
}
if (!FlushFileBuffers(remoteFile)) {
printf("Flush failed: %lu", GetLastError());
return EXIT_FAILURE;
}
if (!CloseHandle(remoteFile)) {
printf("Close failed: %lu", GetLastError());
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}