/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef CacheIOThread_h_
#define CacheIOThread_h_

#include "nsIThreadInternal.h"
#include "nsISupportsImpl.h"
#include "prthread.h"
#include "nsTArray.h"
#include "mozilla/Monitor.h"
#include "mozilla/Atomics.h"
#include "mozilla/UniquePtr.h"

class nsIRunnable;

namespace mozilla {
namespace net {

namespace detail {
// A class keeping platform specific information needed to
// cancel any long blocking synchronous IO.  Must be predeclared here
// since including windows.h breaks stuff with number of macro definition
// conflicts.
class NativeThreadHandle;
}  // namespace detail

class CacheIOThread final : public nsIThreadObserver {
  virtual ~CacheIOThread();

 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSITHREADOBSERVER

  CacheIOThread();

  // We have LAST_LEVEL times queues, so keep the inline storage reasonable.
  using EventQueue = AutoTArray<nsCOMPtr<nsIRunnable>, 32>;

  enum ELevel : uint32_t {
    OPEN_PRIORITY,
    READ_PRIORITY,
    MANAGEMENT,  // Doesn't do any actual I/O
    OPEN,
    READ,
    WRITE_PRIORITY,
    WRITE,
    INDEX,
    EVICT,
    LAST_LEVEL,

    // This is actually executed as the first level, but we want this enum
    // value merely as an indicator while other values are used as indexes
    // to the queue array.  Hence put at end and not as the first.
    XPCOM_LEVEL
  };

  nsresult Init();
  nsresult Dispatch(nsIRunnable* aRunnable, uint32_t aLevel);
  nsresult Dispatch(already_AddRefed<nsIRunnable>, uint32_t aLevel);
  // Makes sure that any previously posted event to OPEN or OPEN_PRIORITY
  // levels (such as file opennings and dooms) are executed before aRunnable
  // that is intended to evict stuff from the cache.
  nsresult DispatchAfterPendingOpens(nsIRunnable* aRunnable);
  bool IsCurrentThread();

  uint32_t QueueSize(bool highPriority);

  uint32_t EventCounter() const { return mEventCounter; }

  /**
   * Callable only on this thread, checks if there is an event waiting in
   * the event queue with a higher execution priority.  If so, the result
   * is true and the current event handler should break it's work and return
   * from Run() method immediately.  The event handler will be rerun again
   * when all more priority events are processed.  Events pending after this
   * handler (i.e. the one that called YieldAndRerun()) will not execute sooner
   * then this handler is executed w/o a call to YieldAndRerun().
   */
  static bool YieldAndRerun() { return sSelf ? sSelf->YieldInternal() : false; }

  void Shutdown();
  // This method checks if there is a long blocking IO on the
  // IO thread and tries to cancel it.  It waits maximum of
  // two seconds.
  void CancelBlockingIO();
  already_AddRefed<nsIEventTarget> Target();

  // A stack class used to annotate running interruptable I/O event
  class Cancelable {
    bool mCancelable;

   public:
    explicit Cancelable(bool aCancelable);
    ~Cancelable();
  };

  // Memory reporting
  size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
  size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;

 private:
  static void ThreadFunc(void* aClosure);
  void ThreadFunc();
  void LoopOneLevel(uint32_t aLevel) MOZ_REQUIRES(mMonitor);
  bool EventsPending(uint32_t aLastLevel = LAST_LEVEL);
  nsresult DispatchInternal(already_AddRefed<nsIRunnable> aRunnable,
                            uint32_t aLevel);
  bool YieldInternal();

  static CacheIOThread* sSelf;

  mozilla::Monitor mMonitor{"CacheIOThread"};
  PRThread* mThread{nullptr};
  // Only set in Init(), before the thread is started, which reads it but never
  // writes
  UniquePtr<detail::NativeThreadHandle> mNativeThreadHandle;
  Atomic<nsIThread*> mXPCOMThread{nullptr};
  Atomic<uint32_t, Relaxed> mLowestLevelWaiting{LAST_LEVEL};
  uint32_t mCurrentlyExecutingLevel{0};  // only accessed on CacheIO Thread

  // Keeps the length of the each event queue, since LoopOneLevel moves all
  // events into a local array.
  Atomic<int32_t> mQueueLength[LAST_LEVEL];

  EventQueue mEventQueue[LAST_LEVEL] MOZ_GUARDED_BY(mMonitor);
  // Raised when nsIEventTarget.Dispatch() is called on this thread
  Atomic<bool, Relaxed> mHasXPCOMEvents{false};
  // See YieldAndRerun() above
  bool mRerunCurrentEvent{false};  // Only accessed on the cache thread
  // Signal to process all pending events and then shutdown
  // Synchronized by mMonitor
  bool mShutdown MOZ_GUARDED_BY(mMonitor){false};
  // If > 0 there is currently an I/O operation on the thread that
  // can be canceled when after shutdown, see the Shutdown() method
  // for usage. Made a counter to allow nesting of the Cancelable class.
  Atomic<uint32_t, Relaxed> mIOCancelableEvents{0};
  // Event counter that increases with every event processed.
  Atomic<uint32_t, Relaxed> mEventCounter{0};
#ifdef DEBUG
  bool mInsideLoop MOZ_GUARDED_BY(mMonitor){true};
#endif
};

}  // namespace net
}  // namespace mozilla

#endif
