// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright contributors to the Ceph project
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#pragma once

#include <mutex>
#include <boost/asio/execution_context.hpp>
#include <boost/intrusive/list.hpp>

namespace ceph::async {

struct service_tag {};
using service_list_base_hook = boost::intrusive::list_base_hook<
    boost::intrusive::tag<service_tag>>;

/// Service for two-phase execution_context shutdown, which breaks ownership
/// cycles between completion handlers and their io objects. Tracks objects
/// which may have outstanding completion handlers, and calls their member
/// function service_shutdown() when the execution_context is shutting down.
/// This member function should destroy any memory associated with its
/// outstanding completion handlers.
///
/// Requirements for IoObject:
/// * Inherits publicly from service_list_base_hook
/// * Has public member function service_shutdown()
/// * Calls add(*this) on construction and remove(*this) on destruction.
template <typename IoObject>
class service : public boost::asio::execution_context::service {
  using base_hook = boost::intrusive::base_hook<service_list_base_hook>;
  boost::intrusive::list<IoObject, base_hook> entries;
  std::mutex mutex;

  /// Called by the execution_context on shutdown
  void shutdown() override {
    while (!entries.empty()) {
      auto& entry = entries.front();
      entries.pop_front();
      entry.service_shutdown();
    }
  }
 public:
  using key_type = service;
  static inline boost::asio::execution_context::id id;

  explicit service(boost::asio::execution_context& ctx)
      : boost::asio::execution_context::service(ctx) {}

  /// Register an io object for notification of service_shutdown()
  void add(IoObject& entry) {
    auto lock = std::scoped_lock{mutex};
    entries.push_back(entry);
  }
  /// Unregister an object
  void remove(IoObject& entry) {
    auto lock = std::scoped_lock{mutex};
    if (entries.empty()) {
      // already shut down
    } else {
      entries.erase(entries.iterator_to(entry));
    }
  }
};

} // namespace ceph::async
