mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
file.c: apply2files releases GVL
This means File.chmod, File.lchmod, File.chown, File.lchown, File.unlink, and File.utime operations on slow filesystems no longer hold up other threads. The platform-specific utime_failed changes is compile-tested using a new UTIME_EINVAL macro This hurts performance on fast filesystem, but these methods are unlikely to be performance bottlenecks and (IMHO) avoiding pathological slowdowns and stalls are more important. benchmark results: minimum results in each 3 measurements. Execution time (sec) name trunk built file_chmod 0.591 0.801 Speedup ratio: compare with the result of `trunk' (greater is better) name built file_chmod 0.737 * file.c (UTIME_EINVAL): new macro to ease compile-testing * file.c (struct apply_arg): new struct * file.c (no_gvl_apply2files): new function * file.c (apply2files): release GVL * file.c (chmod_internal): adjust for apply2files changes * file.c (lchmod_internal): ditto * file.c (chown_internal): ditto * file.c (lchown_internal): ditto * file.c (utime_failed): ditto * file.c (utime_internal): ditto * file.c (unlink_internal): ditto [ruby-core:83200] [Feature #13996] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60386 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
74fa8c15f4
commit
cb9c849af6
2 changed files with 126 additions and 48 deletions
9
benchmark/bm_file_chmod.rb
Normal file
9
benchmark/bm_file_chmod.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# chmod file
|
||||||
|
require 'tempfile'
|
||||||
|
max = 200_000
|
||||||
|
tmp = Tempfile.new('chmod')
|
||||||
|
path = tmp.path
|
||||||
|
max.times do
|
||||||
|
File.chmod(0777, path)
|
||||||
|
end
|
||||||
|
tmp.close!
|
165
file.c
165
file.c
|
@ -126,6 +126,11 @@ int flock(int, int);
|
||||||
# define TO_OSPATH(str) (str)
|
# define TO_OSPATH(str) (str)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* utime may fail if time is out-of-range for the FS [ruby-dev:38277] */
|
||||||
|
#if defined DOSISH || defined __CYGWIN__
|
||||||
|
# define UTIME_EINVAL
|
||||||
|
#endif
|
||||||
|
|
||||||
VALUE rb_cFile;
|
VALUE rb_cFile;
|
||||||
VALUE rb_mFileTest;
|
VALUE rb_mFileTest;
|
||||||
VALUE rb_cStat;
|
VALUE rb_cStat;
|
||||||
|
@ -344,20 +349,87 @@ ignored_char_p(const char *p, const char *e, rb_encoding *enc)
|
||||||
|
|
||||||
#define apply2args(n) (rb_check_arity(argc, n, UNLIMITED_ARGUMENTS), argc-=n)
|
#define apply2args(n) (rb_check_arity(argc, n, UNLIMITED_ARGUMENTS), argc-=n)
|
||||||
|
|
||||||
static VALUE
|
struct apply_arg {
|
||||||
apply2files(void (*func)(const char *, VALUE, void *), int argc, VALUE *argv, void *arg)
|
int i;
|
||||||
{
|
int argc;
|
||||||
long i;
|
int errnum;
|
||||||
VALUE path;
|
int (*func)(const char *, void *);
|
||||||
|
void *arg;
|
||||||
|
struct {
|
||||||
|
const char *ptr;
|
||||||
|
VALUE path;
|
||||||
|
} fn[1]; /* flexible array */
|
||||||
|
};
|
||||||
|
|
||||||
for (i=0; i<argc; i++) {
|
static void *
|
||||||
const char *s;
|
no_gvl_apply2files(void *ptr)
|
||||||
path = rb_get_path(argv[i]);
|
{
|
||||||
path = rb_str_encode_ospath(path);
|
struct apply_arg *aa = ptr;
|
||||||
s = RSTRING_PTR(path);
|
|
||||||
(*func)(s, path, arg);
|
for (aa->i = 0; aa->i < aa->argc; aa->i++) {
|
||||||
|
if (aa->func(aa->fn[aa->i].ptr, aa->arg) < 0) {
|
||||||
|
aa->errnum = errno;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef UTIME_EINVAL
|
||||||
|
NORETURN(static void utime_failed(struct apply_arg *));
|
||||||
|
static int utime_internal(const char *, void *);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
apply2files(int (*func)(const char *, void *), int argc, VALUE *argv, void *arg)
|
||||||
|
{
|
||||||
|
VALUE v;
|
||||||
|
VALUE tmp = Qfalse;
|
||||||
|
size_t size = sizeof(const char *) + sizeof(VALUE);
|
||||||
|
const long len = (long)(sizeof(struct apply_arg) + (size * argc) - size);
|
||||||
|
struct apply_arg *aa = ALLOCV(v, len);
|
||||||
|
|
||||||
|
aa->errnum = 0;
|
||||||
|
aa->argc = argc;
|
||||||
|
aa->arg = arg;
|
||||||
|
aa->func = func;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* aa is on-stack for small argc, we must ensure paths are marked
|
||||||
|
* for large argv if aa is on the heap.
|
||||||
|
*/
|
||||||
|
if (v) {
|
||||||
|
tmp = rb_ary_tmp_new(argc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (aa->i = 0; aa->i < argc; aa->i++) {
|
||||||
|
VALUE path = rb_get_path(argv[aa->i]);
|
||||||
|
|
||||||
|
path = rb_str_encode_ospath(path);
|
||||||
|
aa->fn[aa->i].ptr = RSTRING_PTR(path);
|
||||||
|
aa->fn[aa->i].path = path;
|
||||||
|
|
||||||
|
if (tmp != Qfalse) {
|
||||||
|
rb_ary_push(tmp, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_thread_call_without_gvl(no_gvl_apply2files, aa, RUBY_UBF_IO, 0);
|
||||||
|
if (aa->errnum) {
|
||||||
|
#ifdef UTIME_EINVAL
|
||||||
|
if (func == utime_internal) {
|
||||||
|
utime_failed(aa);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
rb_syserr_fail_path(aa->errnum, aa->fn[aa->i].path);
|
||||||
|
}
|
||||||
|
if (v) {
|
||||||
|
ALLOCV_END(v);
|
||||||
|
}
|
||||||
|
if (tmp != Qfalse) {
|
||||||
|
rb_ary_clear(tmp);
|
||||||
|
rb_gc_force_recycle(tmp);
|
||||||
|
}
|
||||||
return LONG2FIX(argc);
|
return LONG2FIX(argc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2328,11 +2400,10 @@ rb_file_size(VALUE obj)
|
||||||
return OFFT2NUM(st.st_size);
|
return OFFT2NUM(st.st_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
chmod_internal(const char *path, VALUE pathv, void *mode)
|
chmod_internal(const char *path, void *mode)
|
||||||
{
|
{
|
||||||
if (chmod(path, *(int *)mode) < 0)
|
return chmod(path, *(int *)mode);
|
||||||
rb_sys_fail_path(pathv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2404,11 +2475,10 @@ rb_file_chmod(VALUE obj, VALUE vmode)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(HAVE_LCHMOD)
|
#if defined(HAVE_LCHMOD)
|
||||||
static void
|
static int
|
||||||
lchmod_internal(const char *path, VALUE pathv, void *mode)
|
lchmod_internal(const char *path, void *mode)
|
||||||
{
|
{
|
||||||
if (lchmod(path, (int)(VALUE)mode) < 0)
|
return lchmod(path, (int)(VALUE)mode);
|
||||||
rb_sys_fail_path(pathv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2458,12 +2528,11 @@ struct chown_args {
|
||||||
rb_gid_t group;
|
rb_gid_t group;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static int
|
||||||
chown_internal(const char *path, VALUE pathv, void *arg)
|
chown_internal(const char *path, void *arg)
|
||||||
{
|
{
|
||||||
struct chown_args *args = arg;
|
struct chown_args *args = arg;
|
||||||
if (chown(path, args->owner, args->group) < 0)
|
return chown(path, args->owner, args->group);
|
||||||
rb_sys_fail_path(pathv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2535,12 +2604,11 @@ rb_file_chown(VALUE obj, VALUE owner, VALUE group)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(HAVE_LCHOWN)
|
#if defined(HAVE_LCHOWN)
|
||||||
static void
|
static int
|
||||||
lchown_internal(const char *path, VALUE pathv, void *arg)
|
lchown_internal(const char *path, void *arg)
|
||||||
{
|
{
|
||||||
struct chown_args *args = arg;
|
struct chown_args *args = arg;
|
||||||
if (lchown(path, args->owner, args->group) < 0)
|
return lchown(path, args->owner, args->group);
|
||||||
rb_sys_fail_path(pathv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2574,16 +2642,22 @@ struct utime_args {
|
||||||
VALUE atime, mtime;
|
VALUE atime, mtime;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined DOSISH || defined __CYGWIN__
|
#ifdef UTIME_EINVAL
|
||||||
NORETURN(static void utime_failed(VALUE, const struct timespec *, VALUE, VALUE));
|
NORETURN(static void utime_failed(struct apply_arg *));
|
||||||
|
|
||||||
static void
|
static void
|
||||||
utime_failed(VALUE path, const struct timespec *tsp, VALUE atime, VALUE mtime)
|
utime_failed(struct apply_arg *aa)
|
||||||
{
|
{
|
||||||
int e = errno;
|
int e = aa->errnum;
|
||||||
if (tsp && e == EINVAL) {
|
VALUE path = aa->fn[aa->i].path;
|
||||||
|
struct utime_args *ua = aa->arg;
|
||||||
|
|
||||||
|
if (ua->tsp && e == EINVAL) {
|
||||||
VALUE e[2], a = Qnil, m = Qnil;
|
VALUE e[2], a = Qnil, m = Qnil;
|
||||||
int d = 0;
|
int d = 0;
|
||||||
|
VALUE atime = ua->atime;
|
||||||
|
VALUE mtime = ua->mtime;
|
||||||
|
|
||||||
if (!NIL_P(atime)) {
|
if (!NIL_P(atime)) {
|
||||||
a = rb_inspect(atime);
|
a = rb_inspect(atime);
|
||||||
}
|
}
|
||||||
|
@ -2608,14 +2682,12 @@ utime_failed(VALUE path, const struct timespec *tsp, VALUE atime, VALUE mtime)
|
||||||
}
|
}
|
||||||
rb_syserr_fail_path(e, path);
|
rb_syserr_fail_path(e, path);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
#define utime_failed(path, tsp, atime, mtime) rb_sys_fail_path(path)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_UTIMES)
|
#if defined(HAVE_UTIMES)
|
||||||
|
|
||||||
static void
|
static int
|
||||||
utime_internal(const char *path, VALUE pathv, void *arg)
|
utime_internal(const char *path, void *arg)
|
||||||
{
|
{
|
||||||
struct utime_args *v = arg;
|
struct utime_args *v = arg;
|
||||||
const struct timespec *tsp = v->tsp;
|
const struct timespec *tsp = v->tsp;
|
||||||
|
@ -2630,9 +2702,9 @@ utime_internal(const char *path, VALUE pathv, void *arg)
|
||||||
try_utimensat = 0;
|
try_utimensat = 0;
|
||||||
goto no_utimensat;
|
goto no_utimensat;
|
||||||
}
|
}
|
||||||
utime_failed(pathv, tsp, v->atime, v->mtime);
|
return -1; /* calls utime_failed */
|
||||||
}
|
}
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
no_utimensat:
|
no_utimensat:
|
||||||
#endif
|
#endif
|
||||||
|
@ -2644,8 +2716,7 @@ no_utimensat:
|
||||||
tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
|
tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
|
||||||
tvp = tvbuf;
|
tvp = tvbuf;
|
||||||
}
|
}
|
||||||
if (utimes(path, tvp) < 0)
|
return utimes(path, tvp);
|
||||||
utime_failed(pathv, tsp, v->atime, v->mtime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
@ -2657,8 +2728,8 @@ struct utimbuf {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void
|
static int
|
||||||
utime_internal(const char *path, VALUE pathv, void *arg)
|
utime_internal(const char *path, void *arg)
|
||||||
{
|
{
|
||||||
struct utime_args *v = arg;
|
struct utime_args *v = arg;
|
||||||
const struct timespec *tsp = v->tsp;
|
const struct timespec *tsp = v->tsp;
|
||||||
|
@ -2668,8 +2739,7 @@ utime_internal(const char *path, VALUE pathv, void *arg)
|
||||||
utbuf.modtime = tsp[1].tv_sec;
|
utbuf.modtime = tsp[1].tv_sec;
|
||||||
utp = &utbuf;
|
utp = &utbuf;
|
||||||
}
|
}
|
||||||
if (utime(path, utp) < 0)
|
return utime(path, utp);
|
||||||
utime_failed(pathv, tsp, v->atime, v->mtime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -2850,11 +2920,10 @@ rb_readlink(VALUE path, rb_encoding *enc)
|
||||||
#define rb_file_s_readlink rb_f_notimplement
|
#define rb_file_s_readlink rb_f_notimplement
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void
|
static int
|
||||||
unlink_internal(const char *path, VALUE pathv, void *arg)
|
unlink_internal(const char *path, void *arg)
|
||||||
{
|
{
|
||||||
if (unlink(path) < 0)
|
return unlink(path);
|
||||||
rb_sys_fail_path(pathv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue