ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) : m_parent(parent) , m_condvar_shared(create_condvar_sharedmemory_name(parent.get_path()).c_str()) { m_mutex.set_shared_part(InterprocessMutex::SharedPart(), parent.get_path(), "ExternalCommitHelper_ControlMutex"); m_commit_available.set_shared_part(m_condvar_shared.get(), parent.get_path(), "ExternalCommitHelper_CommitCondVar", std::filesystem::temp_directory_path().u8string()); m_thread = std::async(std::launch::async, [this]() { listen(); }); }
ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) : m_parent(parent) { m_epfd = epoll_create(1); if (m_epfd == -1) { throw std::system_error(errno, std::system_category()); } auto path = parent.get_path() + ".note"; // Create and open the named pipe int ret = mkfifo(path.c_str(), 0600); if (ret == -1) { int err = errno; if (err == ENOTSUP) { // Filesystem doesn't support named pipes, so try putting it in tmp instead // Hash collisions are okay here because they just result in doing // extra work, as opposed to correctness problems std::ostringstream ss; std::string tmp_dir(getenv("TMPDIR")); ss << tmp_dir; if (tmp_dir.back() != '/') ss << '/'; ss << "realm_" << std::hash<std::string>()(path) << ".note"; path = ss.str(); ret = mkfifo(path.c_str(), 0600); err = errno; } // the fifo already existing isn't an error if (ret == -1 && err != EEXIST) { throw std::system_error(err, std::system_category()); } } m_notify_fd = open(path.c_str(), O_RDWR); if (m_notify_fd == -1) { throw std::system_error(errno, std::system_category()); } // Make writing to the pipe return -1 when the pipe's buffer is full // rather than blocking until there's space available ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK); if (ret == -1) { throw std::system_error(errno, std::system_category()); } // Create the anonymous pipe int pipe_fd[2]; ret = pipe(pipe_fd); if (ret == -1) { throw std::system_error(errno, std::system_category()); } m_shutdown_read_fd = pipe_fd[0]; m_shutdown_write_fd = pipe_fd[1]; m_thread = std::thread([=] { try { listen(); } catch (std::exception const& e) { LOGE("uncaught exception in notifier thread: %s: %s\n", typeid(e).name(), e.what()); throw; } catch (...) { LOGE("uncaught exception in notifier thread\n"); throw; } }); }
// Listening for external changes is done using kqueue() on a background thread. // kqueue() lets us efficiently wait until the amount of data which can be read // from one or more file descriptors has changed, and tells us which of the file // descriptors it was that changed. We use this to wait on both the shared named // pipe, and a local anonymous pipe. When data is written to the named pipe, we // signal the runloop source and wake up the target runloop, and when data is // written to the anonymous pipe the background thread removes the runloop // source from the runloop and and shuts down. ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent) : m_parent(parent) { m_kq = kqueue(); if (m_kq == -1) { throw std::system_error(errno, std::system_category()); } #if !TARGET_OS_TV auto path = parent.get_path() + ".note"; // Create and open the named pipe int ret = mkfifo(path.c_str(), 0600); if (ret == -1) { int err = errno; if (err == ENOTSUP) { // Filesystem doesn't support named pipes, so try putting it in tmp instead // Hash collisions are okay here because they just result in doing // extra work, as opposed to correctness problems std::ostringstream ss; ss << getenv("TMPDIR"); ss << "realm_" << std::hash<std::string>()(path) << ".note"; path = ss.str(); ret = mkfifo(path.c_str(), 0600); err = errno; } // the fifo already existing isn't an error if (ret == -1 && err != EEXIST) { throw std::system_error(err, std::system_category()); } } m_notify_fd = open(path.c_str(), O_RDWR); if (m_notify_fd == -1) { throw std::system_error(errno, std::system_category()); } // Make writing to the pipe return -1 when the pipe's buffer is full // rather than blocking until there's space available ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK); if (ret == -1) { throw std::system_error(errno, std::system_category()); } #else // !TARGET_OS_TV // tvOS does not support named pipes, so use an anonymous pipe instead int notification_pipe[2]; int ret = pipe(notification_pipe); if (ret == -1) { throw std::system_error(errno, std::system_category()); } m_notify_fd = notification_pipe[0]; m_notify_fd_write = notification_pipe[1]; #endif // TARGET_OS_TV // Create the anonymous pipe for shutdown notifications int shutdown_pipe[2]; ret = pipe(shutdown_pipe); if (ret == -1) { throw std::system_error(errno, std::system_category()); } m_shutdown_read_fd = shutdown_pipe[0]; m_shutdown_write_fd = shutdown_pipe[1]; m_thread = std::async(std::launch::async, [=] { try { listen(); } catch (std::exception const& e) { fprintf(stderr, "uncaught exception in notifier thread: %s: %s\n", typeid(e).name(), e.what()); asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread: %s: %s", typeid(e).name(), e.what()); throw; } catch (...) { fprintf(stderr, "uncaught exception in notifier thread\n"); asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread"); throw; } }); }