folly/folly/io/async/test/IoUringBackendSetupTest.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/experimental/io/IoUringBackend.h>
#include <folly/portability/GTest.h>

namespace folly::test {

TEST(IoUringBackendSetupTest, SetupPollNoGroup) {
  IoUringBackend::Options options;
  options.setFlags(IoUringBackend::Options::Flags::POLL_SQ);

  IoUringBackend io(options);

  EXPECT_TRUE(io.params().flags & IORING_SETUP_CQSIZE);
  EXPECT_TRUE(io.params().flags & IORING_SETUP_SQPOLL);
  EXPECT_FALSE(io.params().flags & IORING_SETUP_SQ_AFF);
  EXPECT_FALSE(io.params().flags & IORING_SETUP_ATTACH_WQ);
}

TEST(IoUringBackendSetupTest, SetupPollWithGroup) {
  IoUringBackend::Options options;
  options.setFlags(IoUringBackend::Options::Flags::POLL_SQ)
      .setSQGroupName("test group")
      .setSQGroupNumThreads(1);

  IoUringBackend io1(options);
  IoUringBackend io2(options);

  // We set up one thread for the group, so the first call should be normal...
  EXPECT_TRUE(io1.params().flags & IORING_SETUP_SQPOLL);
  EXPECT_FALSE(io1.params().flags & IORING_SETUP_SQ_AFF);
  EXPECT_FALSE(io1.params().flags & IORING_SETUP_ATTACH_WQ);

  // second call should have attached to existing fd 1.
  EXPECT_TRUE(io2.params().flags & IORING_SETUP_SQPOLL);
  EXPECT_FALSE(io2.params().flags & IORING_SETUP_SQ_AFF);
  EXPECT_TRUE(io2.params().flags & IORING_SETUP_ATTACH_WQ);
  EXPECT_EQ(io2.params().wq_fd, io1.ioRingPtr()->ring_fd);
}

TEST(IoUringBackendSetupTest, SetupPollWithGroupAndCpu) {
  IoUringBackend::Options options;
  options.setFlags(IoUringBackend::Options::Flags::POLL_SQ)
      .setSQGroupName("test group")
      .setSQGroupNumThreads(2)
      .setSQCpu(1)
      .setSQCpu(0);

  IoUringBackend io1(options);
  IoUringBackend io2(options);
  IoUringBackend io3(options);

  // The first call should create a thread with CPU affinity set.
  EXPECT_TRUE(io1.params().flags & IORING_SETUP_SQPOLL);
  EXPECT_TRUE(io1.params().flags & IORING_SETUP_SQ_AFF);
  EXPECT_FALSE(io1.params().flags & IORING_SETUP_ATTACH_WQ);
  // We don't know which CPU code will choose, but it better be one or the
  // other.
  EXPECT_TRUE(
      io1.params().sq_thread_cpu == 1 || io1.params().sq_thread_cpu == 0);

  // We set two threads, so second call should create a thread with other CPU.
  EXPECT_TRUE(io2.params().flags & IORING_SETUP_CQSIZE);
  EXPECT_TRUE(io2.params().flags & IORING_SETUP_SQPOLL);
  EXPECT_TRUE(io2.params().flags & IORING_SETUP_SQ_AFF);
  EXPECT_FALSE(io2.params().flags & IORING_SETUP_ATTACH_WQ);
  // This one better choose the other CPU.
  EXPECT_TRUE(
      io2.params().sq_thread_cpu == 1 || io2.params().sq_thread_cpu == 0);
  EXPECT_NE(io1.params().sq_thread_cpu, io2.params().sq_thread_cpu);

  // And the third thread should have attached to an existing SQ and not
  // specified an affinity.
  EXPECT_TRUE(io3.params().flags & IORING_SETUP_SQPOLL);
  EXPECT_FALSE(io3.params().flags & IORING_SETUP_SQ_AFF);
  EXPECT_TRUE(io3.params().flags & IORING_SETUP_ATTACH_WQ);
}

} // namespace folly::test