mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-26 19:01:56 -04:00
Rework open file resource management
This commit is contained in:
parent
a47842e2f8
commit
e32985e490
11 changed files with 453 additions and 303 deletions
|
|
@ -29,6 +29,16 @@ EXTERN_C_BEGIN
|
|||
* Basic FUSE API *
|
||||
* ----------------------------------------------------------- */
|
||||
|
||||
#define INVALID_BACKING_ID (0)
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
fuse_backing_id_is_valid(const int backing_id_)
|
||||
{
|
||||
return (backing_id_ > INVALID_BACKING_ID);
|
||||
}
|
||||
|
||||
struct fuse_dirents_t;
|
||||
typedef struct fuse_dirents_t fuse_dirents_t;
|
||||
|
||||
|
|
|
|||
|
|
@ -3878,7 +3878,7 @@ fuse_passthrough_open(const int fd_)
|
|||
|
||||
rv = ::ioctl(dev_fuse_fd,FUSE_DEV_IOC_BACKING_OPEN,&bm);
|
||||
|
||||
return rv;
|
||||
return ((rv < 0) ? INVALID_BACKING_ID : rv);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -3886,6 +3886,9 @@ fuse_passthrough_close(const int backing_id_)
|
|||
{
|
||||
int dev_fuse_fd;
|
||||
|
||||
if(!fuse_backing_id_is_valid(backing_id_))
|
||||
return 0;
|
||||
|
||||
dev_fuse_fd = fuse_chan_fd(f.se->ch);
|
||||
|
||||
return ::ioctl(dev_fuse_fd,FUSE_DEV_IOC_BACKING_CLOSE,&backing_id_);
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ fill_open(struct fuse_open_out *arg_,
|
|||
arg_->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
|
||||
if(ffi_->noflush)
|
||||
arg_->open_flags |= FOPEN_NOFLUSH;
|
||||
if(ffi_->passthrough && (ffi_->backing_id > 0))
|
||||
if(ffi_->passthrough && fuse_backing_id_is_valid(ffi_->backing_id))
|
||||
{
|
||||
arg_->open_flags |= FOPEN_PASSTHROUGH;
|
||||
arg_->backing_id = ffi_->backing_id;
|
||||
|
|
|
|||
|
|
@ -22,13 +22,15 @@
|
|||
|
||||
#include "base_types.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class FileInfo : public FH
|
||||
{
|
||||
public:
|
||||
static FileInfo *from_fh(const u64 fh);
|
||||
static FileInfo *from_fh(const u64);
|
||||
static u64 to_fh(const FileInfo*);
|
||||
|
||||
public:
|
||||
FileInfo(const int fd_,
|
||||
|
|
@ -71,16 +73,25 @@ public:
|
|||
std::mutex mutex;
|
||||
};
|
||||
|
||||
inline
|
||||
u64
|
||||
FileInfo::to_fh(const FileInfo *fi_)
|
||||
{
|
||||
assert(fi_ != nullptr);
|
||||
return reinterpret_cast<u64>(fi_);
|
||||
}
|
||||
|
||||
inline
|
||||
u64
|
||||
FileInfo::to_fh() const
|
||||
{
|
||||
return reinterpret_cast<u64>(this);
|
||||
return FileInfo::to_fh(this);
|
||||
}
|
||||
|
||||
inline
|
||||
FileInfo*
|
||||
FileInfo::from_fh(const u64 fh_)
|
||||
{
|
||||
assert(fh_ != 0);
|
||||
return reinterpret_cast<FileInfo*>(fh_);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#include "fuse.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -225,29 +226,26 @@ _(const PassthroughIOEnum e_,
|
|||
|
||||
static
|
||||
int
|
||||
_create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
const mode_t mode_,
|
||||
fuse_file_info_t *ffi_,
|
||||
State::OpenFile *of_)
|
||||
_create(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
mode_t mode_,
|
||||
fuse_file_info_t *ffi_)
|
||||
{
|
||||
int rv;
|
||||
FileInfo *fi;
|
||||
auto &of = state.open_files;
|
||||
|
||||
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
|
||||
if(cfg.cache_writeback)
|
||||
::_tweak_flags_cache_writeback(&ffi_->flags);
|
||||
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
|
||||
ffi_->flags);
|
||||
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,ffi_->flags);
|
||||
|
||||
rv = ::_create(ctx_,
|
||||
cfg.func.getattr.policy,
|
||||
cfg.func.create.policy,
|
||||
cfg.branches,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
mode_,
|
||||
ctx_->umask);
|
||||
int rv = ::_create(ctx_,
|
||||
cfg.func.getattr.policy,
|
||||
cfg.func.create.policy,
|
||||
cfg.branches,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
mode_,
|
||||
ctx_->umask);
|
||||
if(rv == -EROFS)
|
||||
{
|
||||
cfg.branches.find_and_set_mode_ro();
|
||||
|
|
@ -264,10 +262,8 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
|
|||
if(rv < 0)
|
||||
return rv;
|
||||
|
||||
fi = FileInfo::from_fh(ffi_->fh);
|
||||
|
||||
of_->ref_count = 1;
|
||||
of_->fi = fi;
|
||||
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
|
||||
int backing_id = INVALID_BACKING_ID;
|
||||
|
||||
switch(_(cfg.passthrough_io,ffi_->flags))
|
||||
{
|
||||
|
|
@ -276,85 +272,29 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
|
|||
case _(PassthroughIO::ENUM::RW,O_RDONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_WRONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_RDWR):
|
||||
backing_id = FUSE::passthrough_open(fi->fd);
|
||||
if(fuse_backing_id_is_valid(backing_id))
|
||||
{
|
||||
ffi_->backing_id = backing_id;
|
||||
ffi_->passthrough = true;
|
||||
ffi_->keep_cache = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
of_->backing_id = FUSE::passthrough_open(fi->fd);
|
||||
if(of_->backing_id < 0)
|
||||
return 0;
|
||||
bool inserted = of.try_emplace(ctx_->nodeid,
|
||||
backing_id,
|
||||
fi);
|
||||
|
||||
ffi_->backing_id = of_->backing_id;
|
||||
ffi_->passthrough = true;
|
||||
ffi_->keep_cache = false;
|
||||
// Legit... this should never happen.
|
||||
assert(inserted);
|
||||
(void)inserted;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
auto
|
||||
_create_insert_lambda(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
const mode_t mode_,
|
||||
fuse_file_info_t *ffi_,
|
||||
int *_rv_)
|
||||
{
|
||||
return
|
||||
[=](auto &val_)
|
||||
{
|
||||
*_rv_ = ::_create_for_insert_lambda(ctx_,
|
||||
fusepath_,
|
||||
mode_,
|
||||
ffi_,
|
||||
&val_.second);
|
||||
};
|
||||
}
|
||||
|
||||
// This function should never be called?
|
||||
static
|
||||
inline
|
||||
auto
|
||||
_create_update_lambda()
|
||||
{
|
||||
return
|
||||
[](const auto &val_)
|
||||
{
|
||||
fmt::println(stderr,"CREATE_UPDATE_LAMBDA: THIS SHOULD NOT HAPPEN");
|
||||
SysLog::crit("CREATE_UPDATE_LAMBDA: THIS SHOULD NOT HAPPEN");
|
||||
abort();
|
||||
};
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_create(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
mode_t mode_,
|
||||
fuse_file_info_t *ffi_)
|
||||
{
|
||||
int rv;
|
||||
auto &of = state.open_files;
|
||||
|
||||
rv = -EINVAL;
|
||||
of.try_emplace_and_visit(ctx_->nodeid,
|
||||
::_create_insert_lambda(ctx_,fusepath_,mode_,ffi_,&rv),
|
||||
::_create_update_lambda());
|
||||
|
||||
// Can't abort an emplace_and_visit and can't assume another thread
|
||||
// hasn't created an entry since this failure so erase only if
|
||||
// ref_count is default (0).
|
||||
if(rv < 0)
|
||||
of.erase_if(ctx_->nodeid,
|
||||
[](const auto &val_)
|
||||
{
|
||||
return (val_.second.ref_count <= 0);
|
||||
});
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
FUSE::create(const fuse_req_ctx_t *ctx_,
|
||||
const char *fusepath_,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "config.hpp"
|
||||
#include "errno.hpp"
|
||||
#include "fileinfo.hpp"
|
||||
#include "fs_close.hpp"
|
||||
#include "fs_cow.hpp"
|
||||
#include "fs_fchmod.hpp"
|
||||
#include "fs_lchmod.hpp"
|
||||
|
|
@ -272,146 +273,6 @@ _(const PassthroughIOEnum e_,
|
|||
return ((((uint64_t)e_) << 32) | (m_ & O_ACCMODE));
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_open_for_insert_lambda(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
fuse_file_info_t *ffi_,
|
||||
State::OpenFile *of_)
|
||||
{
|
||||
int rv;
|
||||
FileInfo *fi;
|
||||
|
||||
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
|
||||
|
||||
if(cfg.cache_writeback)
|
||||
::_tweak_flags_cache_writeback(&ffi_->flags);
|
||||
|
||||
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
|
||||
ffi_->flags);
|
||||
|
||||
rv = ::_open(cfg.func.open.policy,
|
||||
cfg.branches,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
cfg.link_cow,
|
||||
cfg.nfsopenhack);
|
||||
|
||||
if(rv < 0)
|
||||
return rv;
|
||||
|
||||
fi = FileInfo::from_fh(ffi_->fh);
|
||||
|
||||
of_->ref_count = 1;
|
||||
of_->fi = fi;
|
||||
|
||||
switch(_(cfg.passthrough_io,ffi_->flags))
|
||||
{
|
||||
case _(PassthroughIO::ENUM::RO,O_RDONLY):
|
||||
case _(PassthroughIO::ENUM::WO,O_WRONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_RDONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_WRONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_RDWR):
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
of_->backing_id = FUSE::passthrough_open(fi->fd);
|
||||
if(of_->backing_id <= 0)
|
||||
return 0;
|
||||
|
||||
ffi_->backing_id = of_->backing_id;
|
||||
ffi_->passthrough = true;
|
||||
ffi_->keep_cache = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_open_for_update_lambda(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
fuse_file_info_t *ffi_,
|
||||
State::OpenFile *of_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
|
||||
|
||||
if(cfg.cache_writeback)
|
||||
::_tweak_flags_cache_writeback(&ffi_->flags);
|
||||
|
||||
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
|
||||
ffi_->flags);
|
||||
|
||||
rv = ::_open_fd(of_->fi->fd,
|
||||
&of_->fi->branch,
|
||||
fusepath_,
|
||||
ffi_);
|
||||
if(rv < 0)
|
||||
return rv;
|
||||
|
||||
of_->ref_count++;
|
||||
|
||||
if(of_->backing_id <= 0)
|
||||
return 0;
|
||||
|
||||
ffi_->backing_id = of_->backing_id;
|
||||
ffi_->passthrough = true;
|
||||
ffi_->keep_cache = false;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
auto
|
||||
_open_insert_lambda(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
fuse_file_info_t *ffi_,
|
||||
int *rv_)
|
||||
{
|
||||
return
|
||||
[=](auto &val_)
|
||||
{
|
||||
*rv_ = ::_open_for_insert_lambda(ctx_,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
&val_.second);
|
||||
};
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
auto
|
||||
_open_update_lambda(const fuse_req_ctx_t *ctx_,
|
||||
const fs::path &fusepath_,
|
||||
fuse_file_info_t *ffi_,
|
||||
int *rv_)
|
||||
{
|
||||
return
|
||||
[=](auto &val_)
|
||||
{
|
||||
// For the edge case where insert succeeded but the open failed
|
||||
// and hadn't been cleaned up yet. There unfortunately is no way
|
||||
// to abort an insert.
|
||||
if(val_.second.ref_count <= 0)
|
||||
{
|
||||
*rv_ = ::_open_for_insert_lambda(ctx_,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
&val_.second);
|
||||
return;
|
||||
}
|
||||
|
||||
*rv_ = ::_open_for_update_lambda(ctx_,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
&val_.second);
|
||||
};
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_open(const fuse_req_ctx_t *ctx_,
|
||||
|
|
@ -421,22 +282,119 @@ _open(const fuse_req_ctx_t *ctx_,
|
|||
int rv;
|
||||
auto &of = state.open_files;
|
||||
|
||||
rv = -EINVAL;
|
||||
of.try_emplace_and_visit(ctx_->nodeid,
|
||||
::_open_insert_lambda(ctx_,fusepath_,ffi_,&rv),
|
||||
::_open_update_lambda(ctx_,fusepath_,ffi_,&rv));
|
||||
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
|
||||
if(cfg.cache_writeback)
|
||||
::_tweak_flags_cache_writeback(&ffi_->flags);
|
||||
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,ffi_->flags);
|
||||
|
||||
// Can't abort an emplace_and_visit and can't assume another thread
|
||||
// hasn't created an entry since this failure so erase only if
|
||||
// ref_count is default (0).
|
||||
if(rv < 0)
|
||||
of.erase_if(ctx_->nodeid,
|
||||
[](auto &val_)
|
||||
while(true)
|
||||
{
|
||||
FileInfo *fi = nullptr;
|
||||
int backing_id = INVALID_BACKING_ID;
|
||||
|
||||
// The ref increment keeps fuse_release.cpp from erasing and
|
||||
// freeing the entry.
|
||||
of.visit(ctx_->nodeid,
|
||||
[&](auto &v_)
|
||||
{
|
||||
v_.second.ref_count++;
|
||||
fi = v_.second.fi;
|
||||
backing_id = v_.second.backing_id;
|
||||
});
|
||||
|
||||
// If the file is already open...
|
||||
if(fi)
|
||||
{
|
||||
rv = ::_open_fd(fi->fd,
|
||||
&fi->branch,
|
||||
fusepath_,
|
||||
ffi_);
|
||||
|
||||
// If we fail to open the already open file we need to treat it
|
||||
// similarly to fuse_release.
|
||||
if(rv < 0)
|
||||
{
|
||||
fi = nullptr;
|
||||
backing_id = INVALID_BACKING_ID;
|
||||
of.erase_if(ctx_->nodeid,
|
||||
[&](auto &v_)
|
||||
{
|
||||
v_.second.ref_count--;
|
||||
if(v_.second.ref_count > 0)
|
||||
return false;
|
||||
|
||||
fi = v_.second.fi;
|
||||
backing_id = v_.second.backing_id;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
FUSE::passthrough_close(backing_id);
|
||||
if(fi)
|
||||
{
|
||||
return (val_.second.ref_count <= 0);
|
||||
});
|
||||
fs::close(fi->fd);
|
||||
delete fi;
|
||||
}
|
||||
|
||||
return rv;
|
||||
return rv;
|
||||
}
|
||||
|
||||
if(!fuse_backing_id_is_valid(backing_id))
|
||||
return 0;
|
||||
|
||||
ffi_->backing_id = backing_id;
|
||||
ffi_->passthrough = true;
|
||||
ffi_->keep_cache = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Was not open, do first open, try to insert, if someone beat us
|
||||
// to it in another thread then throw it away and try again.
|
||||
rv = ::_open(cfg.func.open.policy,
|
||||
cfg.branches,
|
||||
fusepath_,
|
||||
ffi_,
|
||||
cfg.link_cow,
|
||||
cfg.nfsopenhack);
|
||||
if(rv < 0)
|
||||
return rv;
|
||||
|
||||
fi = FileInfo::from_fh(ffi_->fh);
|
||||
|
||||
switch(_(cfg.passthrough_io,ffi_->flags))
|
||||
{
|
||||
case _(PassthroughIO::ENUM::RO,O_RDONLY):
|
||||
case _(PassthroughIO::ENUM::WO,O_WRONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_RDONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_WRONLY):
|
||||
case _(PassthroughIO::ENUM::RW,O_RDWR):
|
||||
backing_id = FUSE::passthrough_open(fi->fd);
|
||||
if(fuse_backing_id_is_valid(backing_id))
|
||||
{
|
||||
ffi_->backing_id = backing_id;
|
||||
ffi_->passthrough = true;
|
||||
ffi_->keep_cache = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bool inserted;
|
||||
|
||||
inserted = of.try_emplace(ctx_->nodeid,
|
||||
backing_id,
|
||||
fi);
|
||||
if(inserted)
|
||||
return 0;
|
||||
|
||||
FUSE::passthrough_close(backing_id);
|
||||
fs::close(fi->fd);
|
||||
delete fi;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,35 +26,6 @@
|
|||
|
||||
#include "fuse.h"
|
||||
|
||||
static
|
||||
constexpr
|
||||
auto
|
||||
_erase_if_lambda(FileInfo *fi_,
|
||||
bool *existed_in_map_)
|
||||
{
|
||||
return
|
||||
[=](auto &val_)
|
||||
{
|
||||
*existed_in_map_ = true;
|
||||
|
||||
if(fi_ != val_.second.fi)
|
||||
{
|
||||
fs::close(fi_->fd);
|
||||
delete fi_;
|
||||
}
|
||||
|
||||
val_.second.ref_count--;
|
||||
if(val_.second.ref_count > 0)
|
||||
return false;
|
||||
|
||||
if(val_.second.backing_id > 0)
|
||||
FUSE::passthrough_close(val_.second.backing_id);
|
||||
fs::close(val_.second.fi->fd);
|
||||
delete val_.second.fi;
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
|
|
@ -62,7 +33,10 @@ _release(const fuse_req_ctx_t *ctx_,
|
|||
FileInfo *fi_,
|
||||
const bool dropcacheonclose_)
|
||||
{
|
||||
bool existed_in_map;
|
||||
int backing_id = INVALID_BACKING_ID;
|
||||
FileInfo *map_fi = nullptr;
|
||||
bool fi_in_map = false;
|
||||
auto &of = state.open_files;
|
||||
|
||||
// according to Feh of nocache calling it once doesn't always work
|
||||
// https://github.com/Feh/nocache
|
||||
|
|
@ -72,34 +46,46 @@ _release(const fuse_req_ctx_t *ctx_,
|
|||
fs::fadvise_dontneed(fi_->fd);
|
||||
}
|
||||
|
||||
// Because of course the API doesn't tell you if the key
|
||||
// existed. Just how many it erased and in this case I only want to
|
||||
// erase if there are no more open files.
|
||||
existed_in_map = false;
|
||||
state.open_files.erase_if(ctx_->nodeid,
|
||||
::_erase_if_lambda(fi_,&existed_in_map));
|
||||
if(existed_in_map)
|
||||
u64 erased;
|
||||
|
||||
erased = of.erase_if(ctx_->nodeid,
|
||||
[&](auto &v_)
|
||||
{
|
||||
fi_in_map = (fi_ == v_.second.fi);
|
||||
|
||||
v_.second.ref_count--;
|
||||
if(v_.second.ref_count > 0)
|
||||
return false;
|
||||
|
||||
backing_id = v_.second.backing_id;
|
||||
map_fi = v_.second.fi;
|
||||
|
||||
return true;
|
||||
});
|
||||
if(not fi_in_map)
|
||||
{
|
||||
fs::close(fi_->fd);
|
||||
delete fi_;
|
||||
}
|
||||
|
||||
if(not erased)
|
||||
return 0;
|
||||
|
||||
fs::close(fi_->fd);
|
||||
delete fi_;
|
||||
FUSE::passthrough_close(backing_id);
|
||||
if(map_fi)
|
||||
{
|
||||
fs::close(map_fi->fd);
|
||||
delete map_fi;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_release(const fuse_req_ctx_t *ctx_,
|
||||
const fuse_file_info_t *ffi_)
|
||||
{
|
||||
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
|
||||
|
||||
return ::_release(ctx_,fi,cfg.dropcacheonclose);
|
||||
}
|
||||
|
||||
int
|
||||
FUSE::release(const fuse_req_ctx_t *ctx_,
|
||||
const fuse_file_info_t *ffi_)
|
||||
{
|
||||
return ::_release(ctx_,ffi_);
|
||||
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
|
||||
|
||||
return ::_release(ctx_,fi,cfg.dropcacheonclose);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
#include "fileinfo.hpp"
|
||||
|
||||
#include "fuse.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
||||
constexpr int INVALID_BACKING_ID = -1;
|
||||
|
||||
class State
|
||||
{
|
||||
public:
|
||||
|
|
@ -26,6 +26,14 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
OpenFile(const int backing_id_,
|
||||
FileInfo * const fi_)
|
||||
: ref_count(1),
|
||||
backing_id(backing_id_),
|
||||
fi(fi_)
|
||||
{
|
||||
}
|
||||
|
||||
int ref_count;
|
||||
int backing_id;
|
||||
FileInfo *fi;
|
||||
|
|
|
|||
103
tests/TEST_io_passthrough_create_open
Executable file
103
tests/TEST_io_passthrough_create_open
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
# Test create + write + read (passthrough.io=rw)
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
|
||||
test_data = b"passthrough io test data for create\n" * 100
|
||||
|
||||
# Write to newly created file
|
||||
bytes_written = os.write(fd, test_data)
|
||||
if bytes_written != len(test_data):
|
||||
print("create write failed: expected {} bytes, wrote {}".format(len(test_data), bytes_written))
|
||||
sys.exit(1)
|
||||
|
||||
# Seek and read back
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
read_data = os.read(fd, len(test_data))
|
||||
if read_data != test_data:
|
||||
print("create read failed: data mismatch")
|
||||
sys.exit(1)
|
||||
|
||||
os.close(fd)
|
||||
|
||||
# Test open existing file + write + read
|
||||
fd = os.open(filepath, os.O_RDWR)
|
||||
|
||||
# Read existing data
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
read_data = os.read(fd, len(test_data))
|
||||
if read_data != test_data:
|
||||
print("open read failed: data mismatch")
|
||||
sys.exit(1)
|
||||
|
||||
# Write more data at end
|
||||
os.lseek(fd, 0, os.SEEK_END)
|
||||
more_data = b"additional passthrough data for open\n" * 50
|
||||
bytes_written = os.write(fd, more_data)
|
||||
if bytes_written != len(more_data):
|
||||
print("open write failed: expected {} bytes, wrote {}".format(len(more_data), bytes_written))
|
||||
sys.exit(1)
|
||||
|
||||
# Verify all data
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
all_data = os.read(fd, len(test_data) + len(more_data))
|
||||
if all_data != test_data + more_data:
|
||||
print("open final read failed: data mismatch")
|
||||
sys.exit(1)
|
||||
|
||||
# Test multiple opens of same file (single backing_id feature)
|
||||
# When a file is already open with passthrough, subsequent opens
|
||||
# must reuse the same backing_id rather than creating new ones
|
||||
fd2 = os.open(filepath, os.O_RDWR)
|
||||
|
||||
# Read from second fd - should see same data
|
||||
os.lseek(fd2, 0, os.SEEK_SET)
|
||||
read_data2 = os.read(fd2, len(test_data) + len(more_data))
|
||||
if read_data2 != test_data + more_data:
|
||||
print("second open read failed: data mismatch")
|
||||
os.close(fd)
|
||||
os.close(fd2)
|
||||
sys.exit(1)
|
||||
|
||||
# Write from second fd
|
||||
os.lseek(fd2, 0, os.SEEK_END)
|
||||
extra_data = b"data from second file descriptor\n" * 25
|
||||
bytes_written = os.write(fd2, extra_data)
|
||||
if bytes_written != len(extra_data):
|
||||
print("second open write failed: expected {} bytes, wrote {}".format(len(extra_data), bytes_written))
|
||||
os.close(fd)
|
||||
os.close(fd2)
|
||||
sys.exit(1)
|
||||
|
||||
# Verify write is visible from first fd (shared backing)
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
combined_data = os.read(fd, len(test_data) + len(more_data) + len(extra_data))
|
||||
expected_data = test_data + more_data + extra_data
|
||||
if combined_data != expected_data:
|
||||
print("cross-fd read failed: data mismatch (writes from fd2 not visible on fd)")
|
||||
os.close(fd)
|
||||
os.close(fd2)
|
||||
sys.exit(1)
|
||||
|
||||
# Open a third fd while others are still open
|
||||
fd3 = os.open(filepath, os.O_RDONLY)
|
||||
os.lseek(fd3, 0, os.SEEK_SET)
|
||||
read_data3 = os.read(fd3, len(expected_data))
|
||||
if read_data3 != expected_data:
|
||||
print("third open read failed: data mismatch")
|
||||
os.close(fd)
|
||||
os.close(fd2)
|
||||
os.close(fd3)
|
||||
sys.exit(1)
|
||||
|
||||
os.close(fd3)
|
||||
os.close(fd2)
|
||||
os.close(fd)
|
||||
|
||||
# Cleanup
|
||||
os.unlink(filepath)
|
||||
132
tests/TEST_io_passthrough_open_race
Executable file
132
tests/TEST_io_passthrough_open_race
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
NUM_THREADS = 50
|
||||
TEST_DATA = b"race condition test data\n"
|
||||
|
||||
def open_and_operate(filepath, barrier, results, index):
|
||||
"""
|
||||
Wait at barrier, then open file, write, read, and store result.
|
||||
"""
|
||||
try:
|
||||
# Wait for all threads to be ready
|
||||
barrier.wait()
|
||||
|
||||
# All threads try to open simultaneously
|
||||
fd = os.open(filepath, os.O_RDWR)
|
||||
|
||||
# Write thread-specific data at a unique offset
|
||||
offset = index * len(TEST_DATA)
|
||||
os.lseek(fd, offset, os.SEEK_SET)
|
||||
bytes_written = os.write(fd, TEST_DATA)
|
||||
|
||||
if bytes_written != len(TEST_DATA):
|
||||
results[index] = ("write_error", "expected {} bytes, wrote {}".format(len(TEST_DATA), bytes_written))
|
||||
os.close(fd)
|
||||
return
|
||||
|
||||
# Read back what we wrote
|
||||
os.lseek(fd, offset, os.SEEK_SET)
|
||||
read_data = os.read(fd, len(TEST_DATA))
|
||||
|
||||
if read_data != TEST_DATA:
|
||||
results[index] = ("read_error", "data mismatch at offset {}".format(offset))
|
||||
os.close(fd)
|
||||
return
|
||||
|
||||
results[index] = ("success", fd)
|
||||
|
||||
except Exception as e:
|
||||
results[index] = ("exception", str(e))
|
||||
|
||||
# Create test file with enough space for all threads
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
|
||||
# Pre-allocate file to avoid issues with concurrent writes extending file
|
||||
total_size = NUM_THREADS * len(TEST_DATA)
|
||||
os.ftruncate(fd, total_size)
|
||||
os.close(fd)
|
||||
|
||||
# Set up synchronization
|
||||
barrier = threading.Barrier(NUM_THREADS)
|
||||
results = [None] * NUM_THREADS
|
||||
threads = []
|
||||
|
||||
# Create and start all threads
|
||||
for i in range(NUM_THREADS):
|
||||
t = threading.Thread(target=open_and_operate, args=(filepath, barrier, results, i))
|
||||
threads.append(t)
|
||||
|
||||
for t in threads:
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
# Check results
|
||||
failed = False
|
||||
fds_to_close = []
|
||||
|
||||
for i, result in enumerate(results):
|
||||
if result is None:
|
||||
print("thread {} returned no result".format(i))
|
||||
failed = True
|
||||
elif result[0] == "success":
|
||||
fds_to_close.append(result[1])
|
||||
else:
|
||||
print("thread {} failed: {} - {}".format(i, result[0], result[1]))
|
||||
failed = True
|
||||
|
||||
if failed:
|
||||
for fd in fds_to_close:
|
||||
os.close(fd)
|
||||
os.unlink(filepath)
|
||||
sys.exit(1)
|
||||
|
||||
# Verify all data is consistent by reading through one fd
|
||||
if fds_to_close:
|
||||
verify_fd = fds_to_close[0]
|
||||
os.lseek(verify_fd, 0, os.SEEK_SET)
|
||||
all_data = os.read(verify_fd, total_size)
|
||||
|
||||
expected_data = TEST_DATA * NUM_THREADS
|
||||
if all_data != expected_data:
|
||||
print("final verification failed: data mismatch")
|
||||
print("expected {} bytes, got {} bytes".format(len(expected_data), len(all_data)))
|
||||
for fd in fds_to_close:
|
||||
os.close(fd)
|
||||
os.unlink(filepath)
|
||||
sys.exit(1)
|
||||
|
||||
# Test cross-fd visibility: write from one fd, read from another
|
||||
if len(fds_to_close) >= 2:
|
||||
fd_a = fds_to_close[0]
|
||||
fd_b = fds_to_close[1]
|
||||
|
||||
# Write new data from fd_a
|
||||
new_data = b"cross-fd visibility test\n"
|
||||
os.lseek(fd_a, 0, os.SEEK_SET)
|
||||
os.write(fd_a, new_data)
|
||||
|
||||
# Read from fd_b - should see the new data
|
||||
os.lseek(fd_b, 0, os.SEEK_SET)
|
||||
read_back = os.read(fd_b, len(new_data))
|
||||
|
||||
if read_back != new_data:
|
||||
print("cross-fd visibility failed: write from fd_a not visible on fd_b")
|
||||
for fd in fds_to_close:
|
||||
os.close(fd)
|
||||
os.unlink(filepath)
|
||||
sys.exit(1)
|
||||
|
||||
# Close all file descriptors
|
||||
for fd in fds_to_close:
|
||||
os.close(fd)
|
||||
|
||||
# Cleanup
|
||||
os.unlink(filepath)
|
||||
|
|
@ -123,12 +123,11 @@ test_str_stuff()
|
|||
void
|
||||
test_config_branches()
|
||||
{
|
||||
uint64_t minfreespace;
|
||||
Branches b(minfreespace);
|
||||
Branches b;
|
||||
Branches::Ptr bcp0;
|
||||
Branches::Ptr bcp1;
|
||||
|
||||
minfreespace = 1234;
|
||||
b.minfreespace = 1234;
|
||||
TEST_CHECK(b->minfreespace() == 1234);
|
||||
TEST_CHECK(b.to_string() == "");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue