macOS下aio_write执行失败:Inappropriate ioctl for device
macOS下Posix AIO异步读写Poll返回"Inappropriate ioctl for device"错误排查
在macOS环境下基于Posix AIO库实现异步读写,IO请求提交环节正常,但调用Poll方法获取执行结果时,返回的finish code不为零,报错信息为"Inappropriate ioctl for device"。已知Posix AIO是用户态异步IO引擎,理论支持跨平台运行,不清楚该错误的触发原因。
异步读写实现代码
#include "io_engine.h" namespace demo { Status PosixAIOEngine::AsyncWrite(int fd, uint64_t offset, const char* buffer, uint64_t size, char** cb) { // TODO Check io_depth requests_.push_back({.aio_req_ = {}, .type_ = kAsyncWrite}); auto& request = requests_.back(); memset(&request.aio_req_, 0, sizeof(struct aiocb)); request.aio_req_.aio_offset = (off_t)offset; request.aio_req_.aio_buf = (void*)buffer; request.aio_req_.aio_nbytes = size; request.aio_req_.aio_fildes = fd; int ret = aio_write(&request.aio_req_); if (ret == -1) { auto msg = "aio_write failed, err msg: " + std::string(strerror(errno)); LOG(ERROR, msg); return Status::IOError(msg); } return Status::OK(); } Status PosixAIOEngine::AsyncRead(int fd, uint64_t offset, char* buffer, uint64_t size, char** cb) { requests_.push_back({.aio_req_ = {}, .type_ = kAsyncRead}); auto& request = requests_.back(); memset(&request.aio_req_, 0, sizeof(struct aiocb)); request.aio_req_.aio_offset = (off_t)offset; request.aio_req_.aio_buf = (void*)buffer; request.aio_req_.aio_nbytes = size; request.aio_req_.aio_fildes = fd; int ret = aio_read(&request.aio_req_); if (ret == -1) { LOG(ERROR, "aio_read failed, err: {}, offset: {}, sz: {}", strerror(errno), offset, size); requests_.pop_back(); return Status::IOError("aio read failed!"); } return Status::OK(); } // TODO Probably we should check the event by their submission order? uint32_t PosixAIOEngine::Poll() { uint32_t cnt = 0; auto it = requests_.begin(); while (it != requests_.end()) { auto& req = *it; int ret = aio_error(&req.aio_req_); if (ret == 0) { LOG(INFO, IORequest::GetTypeName(req.type_) + ", callback offset: " + std::to_string(req.aio_req_.aio_offset)); uint64_t finish_code = aio_return(&req.aio_req_); if (finish_code != 0) { LOG(ERROR, "return code error : {}, offset: {}", strerror(errno), req.aio_req_.aio_offset); } it = requests_.erase(it); cnt++; } else { ++it; } } return cnt; } }
测试代码
#include "io_engine.h" #include <fcntl.h> #include <gtest/gtest.h> #include <unistd.h> #include <memory> #include "utils.h" namespace demo { class IOEngineTest : public ::testing::Test { public: std::string file_prefix = "io_engine_test_file_"; std::string test_file_; int write_fd_{}; int read_fd_{}; uint64_t file_size_ = 10 << 20; std::unique_ptr<IOEngine> io_engine_; void SetUp() override { test_file_ = CreateRandomFile(file_size_); io_engine_ = std::make_unique<PosixAIOEngine>(); write_fd_ = open(test_file_.c_str(), O_RDWR | O_CREAT, 0777); if (write_fd_ == -1) { LOG(ERROR, "Open writable file failed, error: {}", std::strerror(errno)); abort(); } read_fd_ = open(test_file_.c_str(), O_RDONLY); if (read_fd_ == -1) { LOG(ERROR, "Open readonly file failed, error: {}", std::strerror(errno)); abort(); } } void TearDown() override { int total = FileUtils::DeleteFilesByPrefix(".", file_prefix); LOG(INFO, "{} files was deleted", total); } std::string CreateRandomFile(uint32_t sz) { auto filename = FileUtils::GenerateRandomFile(file_prefix, sz); filenames_.emplace_back(filename); return filename; } private: std::vector<std::string> filenames_; }; TEST_F(IOEngineTest, MultipleAIOWriteTest) { uint64_t page_size = 4UL << 10; uint32_t total_requests = 6; // Write Request for (int i = 0; i < total_requests; ++i) { std::string data = std::to_string(i); auto s = io_engine_->AsyncWrite(write_fd_, i * page_size, data.c_str(), data.size(), nullptr); EXPECT_TRUE(s.ok()); } // POLL result out uint32_t cnt = 0; while (cnt < total_requests) { cnt += io_engine_->Poll(); } EXPECT_EQ(cnt, total_requests); EXPECT_EQ(io_engine_->GetInFlightRequests(), 0); // Read Request char* buf = nullptr; posix_memalign((void**)&buf, page_size, total_requests * page_size); for (int i = 0; i < total_requests; ++i) { auto s = io_engine_->AsyncRead(read_fd_, i * page_size, buf, page_size, nullptr); ASSERT_TRUE(s.ok()); } EXPECT_EQ(io_engine_->GetInFlightRequests(), total_requests); // POLL result out cnt = 0; while (cnt < total_requests) { cnt += io_engine_->Poll(); } EXPECT_EQ(cnt, total_requests); for (int i = 0; i < total_requests; ++i) { ASSERT_EQ(buf[i], static_cast<char>(i)); } delete buf; } }
日志信息
[2024-03-11 T11:01:36.786][132731804][info][utils.h:35] Generate random file, filename: io_engine_test_file_4245618977 [2024-03-11 T11:01:36.786][132731804][info][io_engine_test.cc:34] write fd: 4, read fd: 5 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncWrite, callback offset: 4096 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 4096, fd : 4 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncWrite, callback offset: 8192 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 8192, fd : 4 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncWrite, callback offset: 12288 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 12288, fd : 4 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncWrite, callback offset: 0 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 0, fd : 4 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncWrite, callback offset: 16384 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 16384, fd : 4 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncWrite, callback offset: 20480 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 20480, fd : 4 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncRead, callback offset: 4096 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 4096, fd : 5 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncRead, callback offset: 12288 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 12288, fd : 5 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncRead, callback offset: 16384 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 16384, fd : 5 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncRead, callback offset: 20480 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 20480, fd : 5 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncRead, callback offset: 0 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 0, fd : 5 [2024-03-11 T11:01:36.787][132731804][info][io_engine.cc:62] kAsyncRead, callback offset: 8192 [2024-03-11 T11:01:36.787][132731804][error][io_engine.cc:66] return code error : Inappropriate ioctl for device, offset: 8192, fd : 5
问题原因及解决方法
核心原因
- macOS对Posix AIO的兼容性限制:macOS的Posix AIO实现并非完全遵循POSIX标准,仅提供有限支持,常规文件场景下容易出现兼容性问题。
- 错误的结果判断逻辑:
aio_return返回的是实际读写的字节数,而非错误码。当前代码误将非零值判定为错误,实际上只有当aio_error返回-1时,才需要通过errno获取错误信息。
修正步骤
1. 修复Poll方法的错误判断逻辑
将aio_return的非零值判定逻辑改为仅当返回-1时才判定为错误,同时正确处理aio_error的返回值:
uint32_t PosixAIOEngine::Poll() { uint32_t cnt = 0; auto it = requests_.begin(); while (it != requests_.end()) { auto& req = *it; int ret = aio_error(&req.aio_req_); if (ret == 0) { LOG(INFO, IORequest::GetTypeName(req.type_) + ", callback offset: " + std::to_string(req.aio_req_.aio_offset)); ssize_t finish_code = aio_return(&req.aio_req_); if (finish_code == -1) { LOG(ERROR, "return code error : {}, offset: {}", strerror(errno), req.aio_req_.aio_offset); } else { LOG(INFO, "{} bytes processed, offset: {}", finish_code, req.aio_req_.aio_offset); } it = requests_.erase(it); cnt++; } else if (ret == EINPROGRESS) { ++it; } else { LOG(ERROR, "Request failed: {}, offset: {}", strerror(ret), req.aio_req_.aio_offset); it = requests_.erase(it); cnt++; } } return cnt; }
2. 替换macOS下的异步IO实现
如果需要稳定的异步IO能力,建议改用macOS原生支持的方案:
- 使用
libdispatch(GCD)的异步文件操作接口,如dispatch_io_create系列函数。 - 使用
kqueue监听文件描述符的读写事件,实现异步IO逻辑。
内容的提问来源于stack exchange,提问作者WoooHaaaa
相关产品推荐
相关产品推荐

