dir.c: openat calls release GVL, too

openat(2) also performs a path lookup, so it is also subject
to pathological slowdowns like opendir(3) and open(2) syscalls.

* dir.c (struct opendir_at_arg): new struct for callback
  (with_gvl_gc_for_fd): new callback for rb_thread_call_with_gvl
  (gc_for_fd_with_gvl): moved up
  (nogvl_opendir_at): extracted from do_opendir
  (opendir_at): new wrapper to release GVL for opendir_at
  (do_opendir): use new wrappers to release GVL
  (nogvl_dir_empty_p): adjust for gc_for_fd_with_gvl

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60831 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
normal 2017-11-18 02:01:49 +00:00
Родитель 089eb0c5ce
Коммит 8840b033fb
1 изменённых файлов: 78 добавлений и 43 удалений

121
dir.c
Просмотреть файл

@ -1404,18 +1404,80 @@ do_lstat(int fd, const char *path, struct stat *pst, int flags, rb_encoding *enc
#define do_lstat do_stat
#endif
struct opendir_at_arg {
int basefd;
const char *path;
};
static void *
with_gvl_gc_for_fd(void *ptr)
{
int *e = ptr;
return (void *)(rb_gc_for_fd(*e) ? Qtrue : Qfalse);
}
static int
gc_for_fd_with_gvl(int e)
{
return (int)(VALUE)rb_thread_call_with_gvl(with_gvl_gc_for_fd, &e);
}
static void *
nogvl_opendir_at(void *ptr)
{
const struct opendir_at_arg *oaa = ptr;
DIR *dirp;
#if USE_OPENDIR_AT
const int opendir_flags = (O_RDONLY|O_CLOEXEC|
# ifdef O_DIRECTORY
O_DIRECTORY|
# endif /* O_DIRECTORY */
0);
int fd = openat(oaa->basefd, oaa->path, 0, opendir_flags);
dirp = fd >= 0 ? fdopendir(fd) : 0;
if (!dirp) {
int e = errno;
switch (gc_for_fd_with_gvl(e)) {
default:
if (fd < 0) fd = openat(oaa->basefd, oaa->path, 0, opendir_flags);
if (fd >= 0) dirp = fdopendir(fd);
if (dirp) return dirp;
e = errno;
/* fallthrough*/
case 0:
if (fd >= 0) close(fd);
errno = e;
}
}
#else /* !USE_OPENDIR_AT */
dirp = opendir(oaa->path);
if (!dirp && gc_for_fd_with_gvl(errno))
dirp = opendir(oaa->path);
#endif /* !USE_OPENDIR_AT */
return dirp;
}
static DIR *
opendir_at(int basefd, const char *path)
{
struct opendir_at_arg oaa;
oaa.basefd = basefd;
oaa.path = path;
return rb_thread_call_without_gvl(nogvl_opendir_at, &oaa, RUBY_UBF_IO, 0);
}
static DIR *
do_opendir(const int basefd, const char *path, int flags, rb_encoding *enc,
ruby_glob_errfunc *errfunc, VALUE arg, int *status)
{
#if USE_OPENDIR_AT
const int opendir_flags = (O_RDONLY|O_CLOEXEC|
#ifdef O_DIRECTORY
O_DIRECTORY|
#endif
0);
int fd;
#endif
DIR *dirp;
#ifdef _WIN32
VALUE tmp = 0;
@ -1425,37 +1487,18 @@ do_opendir(const int basefd, const char *path, int flags, rb_encoding *enc,
path = RSTRING_PTR(tmp);
}
#endif
#if USE_OPENDIR_AT
fd = openat(basefd, path, 0, opendir_flags);
dirp = (fd < 0) ? NULL : fdopendir(fd);
#else
dirp = opendir_without_gvl(path);
#endif
dirp = opendir_at(basefd, path);
if (!dirp) {
int e = errno;
switch (rb_gc_for_fd(e)) {
default:
#if USE_OPENDIR_AT
if ((fd >= 0) || (fd = openat(basefd, path, 0, opendir_flags)) >= 0) {
dirp = fdopendir(fd);
}
#else
dirp = opendir_without_gvl(path);
#endif
if (dirp) break;
e = errno;
/* fallback */
case 0:
#if USE_OPENDIR_AT
if (fd >= 0) close(fd);
#endif
*status = 0;
if (to_be_ignored(e)) break;
*status = 0;
if (!to_be_ignored(e)) {
if (errfunc) {
*status = (*errfunc)(path, arg, enc, e);
break;
}
sys_warning(path, enc);
else {
sys_warning(path, enc);
}
}
}
#ifdef _WIN32
@ -3040,14 +3083,6 @@ rb_dir_exists_p(VALUE obj, VALUE fname)
return rb_file_directory_p(obj, fname);
}
static void *
gc_for_fd_with_gvl(void *ptr)
{
int *e = ptr;
return (void *)(rb_gc_for_fd(*e) ? Qtrue : Qfalse);
}
static void *
nogvl_dir_empty_p(void *ptr)
{
@ -3058,7 +3093,7 @@ nogvl_dir_empty_p(void *ptr)
if (!dir) {
int e = errno;
switch ((int)(VALUE)rb_thread_call_with_gvl(gc_for_fd_with_gvl, &e)) {
switch (gc_for_fd_with_gvl(e)) {
default:
dir = opendir(path);
if (dir) break;