mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
1045 lines
39 KiB
C
1045 lines
39 KiB
C
|
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
#ifndef NET_TOOLS_EPOLL_SERVER_EPOLL_SERVER_H_
|
||
|
#define NET_TOOLS_EPOLL_SERVER_EPOLL_SERVER_H_
|
||
|
|
||
|
#include <fcntl.h>
|
||
|
#include <stddef.h>
|
||
|
#include <stdint.h>
|
||
|
#include <sys/queue.h>
|
||
|
|
||
|
#include <map>
|
||
|
#include <memory>
|
||
|
#include <string>
|
||
|
#include <unordered_map>
|
||
|
#include <unordered_set>
|
||
|
#include <vector>
|
||
|
|
||
|
// #define EPOLL_SERVER_EVENT_TRACING 1
|
||
|
//
|
||
|
// Defining EPOLL_SERVER_EVENT_TRACING
|
||
|
// causes code to exist which didn't before.
|
||
|
// This code tracks each event generated by the epollserver,
|
||
|
// as well as providing a per-fd-registered summary of
|
||
|
// events. Note that enabling this code vastly slows
|
||
|
// down operations, and uses substantially more
|
||
|
// memory. For these reasons, it should only be enabled by developers doing
|
||
|
// development at their workstations.
|
||
|
//
|
||
|
// A structure called 'EventRecorder' will exist when
|
||
|
// the macro is defined. See the EventRecorder class interface
|
||
|
// within the EpollServer class for more details.
|
||
|
#ifdef EPOLL_SERVER_EVENT_TRACING
|
||
|
#include <ostream>
|
||
|
#include "base/logging.h"
|
||
|
#endif
|
||
|
|
||
|
#include "base/compiler_specific.h"
|
||
|
#include "base/macros.h"
|
||
|
#include <sys/epoll.h>
|
||
|
|
||
|
namespace net {
|
||
|
|
||
|
class EpollServer;
|
||
|
class EpollAlarmCallbackInterface;
|
||
|
class ReadPipeCallback;
|
||
|
|
||
|
struct EpollEvent {
|
||
|
EpollEvent(int events)
|
||
|
: in_events(events),
|
||
|
out_ready_mask(0) {
|
||
|
}
|
||
|
|
||
|
int in_events; // incoming events
|
||
|
int out_ready_mask; // the new event mask for ready list (0 means don't
|
||
|
// get on the ready list). This field is always
|
||
|
// initialized to 0 when the event is passed to
|
||
|
// OnEvent.
|
||
|
};
|
||
|
|
||
|
// Callbacks which go into EpollServers are expected to derive from this class.
|
||
|
class EpollCallbackInterface {
|
||
|
public:
|
||
|
// Summary:
|
||
|
// Called when the callback is registered into a EpollServer.
|
||
|
// Args:
|
||
|
// eps - the poll server into which this callback was registered
|
||
|
// fd - the file descriptor which was registered
|
||
|
// event_mask - the event mask (composed of EPOLLIN, EPOLLOUT, etc)
|
||
|
// which was registered (and will initially be used
|
||
|
// in the epoll() calls)
|
||
|
virtual void OnRegistration(EpollServer* eps, int fd, int event_mask) = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called when the event_mask is modified (for a file-descriptor)
|
||
|
// Args:
|
||
|
// fd - the file descriptor which was registered
|
||
|
// event_mask - the event mask (composed of EPOLLIN, EPOLLOUT, etc)
|
||
|
// which was is now curren (and will be used
|
||
|
// in subsequent epoll() calls)
|
||
|
virtual void OnModification(int fd, int event_mask) = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called whenever an event occurs on the file-descriptor.
|
||
|
// This is where the bulk of processing is expected to occur.
|
||
|
// Args:
|
||
|
// fd - the file descriptor which was registered
|
||
|
// event - a struct that contains the event mask (composed of EPOLLIN,
|
||
|
// EPOLLOUT, etc), a flag that indicates whether this is a true
|
||
|
// epoll_wait event vs one from the ready list, and an output
|
||
|
// parameter for OnEvent to inform the EpollServer whether to put
|
||
|
// this fd on the ready list.
|
||
|
virtual void OnEvent(int fd, EpollEvent* event) = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called when the file-descriptor is unregistered from the poll-server.
|
||
|
// Args:
|
||
|
// fd - the file descriptor which was registered, and of this call, is now
|
||
|
// unregistered.
|
||
|
// replaced - If true, this callback is being replaced by another, otherwise
|
||
|
// it is simply being removed.
|
||
|
virtual void OnUnregistration(int fd, bool replaced) = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called when the epoll server is shutting down. This is different from
|
||
|
// OnUnregistration because the subclass may want to clean up memory.
|
||
|
// This is called in leiu of OnUnregistration.
|
||
|
// Args:
|
||
|
// fd - the file descriptor which was registered.
|
||
|
virtual void OnShutdown(EpollServer* eps, int fd) = 0;
|
||
|
|
||
|
virtual ~EpollCallbackInterface() {}
|
||
|
|
||
|
protected:
|
||
|
EpollCallbackInterface() {}
|
||
|
};
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
class EpollServer {
|
||
|
public:
|
||
|
typedef EpollAlarmCallbackInterface AlarmCB;
|
||
|
typedef EpollCallbackInterface CB;
|
||
|
|
||
|
typedef std::multimap<int64_t, AlarmCB*> TimeToAlarmCBMap;
|
||
|
typedef TimeToAlarmCBMap::iterator AlarmRegToken;
|
||
|
|
||
|
// Summary:
|
||
|
// Constructor:
|
||
|
// By default, we don't wait any amount of time for events, and
|
||
|
// we suggest to the epoll-system that we're going to use on-the-order
|
||
|
// of 1024 FDs.
|
||
|
EpollServer();
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Destructor
|
||
|
virtual ~EpollServer();
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary
|
||
|
// Register a callback to be called whenever an event contained
|
||
|
// in the set of events included in event_mask occurs on the
|
||
|
// file-descriptor 'fd'
|
||
|
//
|
||
|
// Note that only one callback is allowed to be registered for
|
||
|
// any specific file-decriptor.
|
||
|
//
|
||
|
// If a callback is registered for a file-descriptor which has already
|
||
|
// been registered, then the previous callback is unregistered with
|
||
|
// the 'replaced' flag set to true. I.e. the previous callback's
|
||
|
// OnUnregistration() function is called like so:
|
||
|
// OnUnregistration(fd, true);
|
||
|
//
|
||
|
// The epoll server does NOT take on ownership of the callback: the callback
|
||
|
// creator is responsible for managing that memory.
|
||
|
//
|
||
|
// Args:
|
||
|
// fd - a valid file-descriptor
|
||
|
// cb - an instance of a subclass of EpollCallbackInterface
|
||
|
// event_mask - a combination of (EPOLLOUT, EPOLLIN.. etc) indicating
|
||
|
// the events for which the callback would like to be
|
||
|
// called.
|
||
|
virtual void RegisterFD(int fd, CB* cb, int event_mask);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// A shortcut for RegisterFD which sets things up such that the
|
||
|
// callback is called when 'fd' is available for writing.
|
||
|
// Args:
|
||
|
// fd - a valid file-descriptor
|
||
|
// cb - an instance of a subclass of EpollCallbackInterface
|
||
|
virtual void RegisterFDForWrite(int fd, CB* cb);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// A shortcut for RegisterFD which sets things up such that the
|
||
|
// callback is called when 'fd' is available for reading or writing.
|
||
|
// Args:
|
||
|
// fd - a valid file-descriptor
|
||
|
// cb - an instance of a subclass of EpollCallbackInterface
|
||
|
virtual void RegisterFDForReadWrite(int fd, CB* cb);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// A shortcut for RegisterFD which sets things up such that the
|
||
|
// callback is called when 'fd' is available for reading.
|
||
|
// Args:
|
||
|
// fd - a valid file-descriptor
|
||
|
// cb - an instance of a subclass of EpollCallbackInterface
|
||
|
virtual void RegisterFDForRead(int fd, CB* cb);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Removes the FD and the associated callback from the pollserver.
|
||
|
// If the callback is registered with other FDs, they will continue
|
||
|
// to be processed using the callback without modification.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the file-descriptor which should no-longer be monitored.
|
||
|
virtual void UnregisterFD(int fd);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modifies the event mask for the file-descriptor, replacing
|
||
|
// the old event_mask with the new one specified here.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the fd whose event mask should be modified.
|
||
|
// event_mask - the new event mask.
|
||
|
virtual void ModifyCallback(int fd, int event_mask);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modifies the event mask for the file-descriptor such that we
|
||
|
// no longer request events when 'fd' is readable.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the fd whose event mask should be modified.
|
||
|
virtual void StopRead(int fd);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modifies the event mask for the file-descriptor such that we
|
||
|
// request events when 'fd' is readable.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the fd whose event mask should be modified.
|
||
|
virtual void StartRead(int fd);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modifies the event mask for the file-descriptor such that we
|
||
|
// no longer request events when 'fd' is writable.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the fd whose event mask should be modified.
|
||
|
virtual void StopWrite(int fd);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modifies the event mask for the file-descriptor such that we
|
||
|
// request events when 'fd' is writable.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the fd whose event mask should be modified.
|
||
|
virtual void StartWrite(int fd);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Looks up the callback associated with the file-desriptor 'fd'.
|
||
|
// If a callback is associated with this file-descriptor, then
|
||
|
// it's OnEvent() method is called with the file-descriptor 'fd',
|
||
|
// and event_mask 'event_mask'
|
||
|
//
|
||
|
// If no callback is registered for this file-descriptor, nothing
|
||
|
// will happen as a result of this call.
|
||
|
//
|
||
|
// This function is used internally by the EpollServer, but is
|
||
|
// available publically so that events might be 'faked'. Calling
|
||
|
// this function with an fd and event_mask is equivalent (as far
|
||
|
// as the callback is concerned) to having a real event generated
|
||
|
// by epoll (except, of course, that read(), etc won't necessarily
|
||
|
// be able to read anything)
|
||
|
// Args:
|
||
|
// fd - the file-descriptor on which an event has occured.
|
||
|
// event_mask - a bitmask representing the events which have occured
|
||
|
// on/for this fd. This bitmask is composed of
|
||
|
// POLLIN, POLLOUT, etc.
|
||
|
//
|
||
|
void HandleEvent(int fd, int event_mask);
|
||
|
|
||
|
// Summary:
|
||
|
// Call this when you want the pollserver to
|
||
|
// wait for events and execute the callbacks associated with
|
||
|
// the file-descriptors on which those events have occured.
|
||
|
// Depending on the value of timeout_in_us_, this may or may
|
||
|
// not return immediately. Please reference the set_timeout()
|
||
|
// function for the specific behaviour.
|
||
|
virtual void WaitForEventsAndExecuteCallbacks();
|
||
|
|
||
|
// Summary:
|
||
|
// When an fd is registered to use edge trigger notification, the ready
|
||
|
// list can be used to simulate level trigger semantics. Edge trigger
|
||
|
// registration doesn't send an initial event, and only rising edge (going
|
||
|
// from blocked to unblocked) events are sent. A callback can put itself on
|
||
|
// the ready list by calling SetFDReady() after calling RegisterFD(). The
|
||
|
// OnEvent method of all callbacks associated with the fds on the ready
|
||
|
// list will be called immediately after processing the events returned by
|
||
|
// epoll_wait(). The fd is removed from the ready list before the
|
||
|
// callback's OnEvent() method is invoked. To stay on the ready list, the
|
||
|
// OnEvent() (or some function in that call chain) must call SetFDReady
|
||
|
// again. When a fd is unregistered using UnregisterFD(), the fd is
|
||
|
// automatically removed from the ready list.
|
||
|
//
|
||
|
// When the callback for a edge triggered fd hits the falling edge (about
|
||
|
// to block, either because of it got an EAGAIN, or had a short read/write
|
||
|
// operation), it should remove itself from the ready list using
|
||
|
// SetFDNotReady() (since OnEvent cannot distinguish between invocation
|
||
|
// from the ready list vs from a normal epoll event). All four ready list
|
||
|
// methods are safe to be called within the context of the callbacks.
|
||
|
//
|
||
|
// Since the ready list invokes EpollCallbackInterface::OnEvent, only fds
|
||
|
// that are registered with the EpollServer will be put on the ready list.
|
||
|
// SetFDReady() and SetFDNotReady() will do nothing if the EpollServer
|
||
|
// doesn't know about the fd passed in.
|
||
|
//
|
||
|
// Since the ready list cannot reliably determine proper set of events
|
||
|
// which should be sent to the callback, SetFDReady() requests the caller
|
||
|
// to provide the ready list with the event mask, which will be used later
|
||
|
// when OnEvent() is invoked by the ready list. Hence, the event_mask
|
||
|
// passedto SetFDReady() does not affect the actual epoll registration of
|
||
|
// the fd with the kernel. If a fd is already put on the ready list, and
|
||
|
// SetFDReady() is called again for that fd with a different event_mask,
|
||
|
// the event_mask will be updated.
|
||
|
virtual void SetFDReady(int fd, int events_to_fake);
|
||
|
|
||
|
virtual void SetFDNotReady(int fd);
|
||
|
|
||
|
// Summary:
|
||
|
// IsFDReady(), ReadyListSize(), and VerifyReadyList are intended as
|
||
|
// debugging tools and for writing unit tests.
|
||
|
// ISFDReady() returns whether a fd is in the ready list.
|
||
|
// ReadyListSize() returns the number of fds on the ready list.
|
||
|
// VerifyReadyList() checks the consistency of internal data structure. It
|
||
|
// will CHECK if it finds an error.
|
||
|
virtual bool IsFDReady(int fd) const;
|
||
|
|
||
|
size_t ReadyListSize() const { return ready_list_size_; }
|
||
|
|
||
|
void VerifyReadyList() const;
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Registers an alarm 'ac' to go off at time 'timeout_time_in_us'.
|
||
|
// If the callback returns a positive number from its OnAlarm() function,
|
||
|
// then the callback will be re-registered at that time, else the alarm
|
||
|
// owner is responsible for freeing up memory.
|
||
|
//
|
||
|
// Important: A give AlarmCB* can not be registered again if it is already
|
||
|
// registered. If a user wants to register a callback again it should first
|
||
|
// unregister the previous callback before calling RegisterAlarm again.
|
||
|
// Args:
|
||
|
// timeout_time_in_us - the absolute time at which the alarm should go off
|
||
|
// ac - the alarm which will be called.
|
||
|
virtual void RegisterAlarm(int64_t timeout_time_in_us, AlarmCB* ac);
|
||
|
|
||
|
// Summary:
|
||
|
// Registers an alarm 'ac' to go off at time: (ApproximateNowInUs() +
|
||
|
// delta_in_us). While this is somewhat less accurate (see the description
|
||
|
// for ApproximateNowInUs() to see how 'approximate'), the error is never
|
||
|
// worse than the amount of time it takes to process all events in one
|
||
|
// WaitForEvents. As with 'RegisterAlarm()', if the callback returns a
|
||
|
// positive number from its OnAlarm() function, then the callback will be
|
||
|
// re-registered at that time, else the alarm owner is responsible for
|
||
|
// freeing up memory.
|
||
|
// Note that this function is purely a convienence. The
|
||
|
// same thing may be accomplished by using RegisterAlarm with
|
||
|
// ApproximateNowInUs() directly.
|
||
|
//
|
||
|
// Important: A give AlarmCB* can not be registered again if it is already
|
||
|
// registered. If a user wants to register a callback again it should first
|
||
|
// unregister the previous callback before calling RegisterAlarm again.
|
||
|
// Args:
|
||
|
// delta_in_us - the delta in microseconds from the ApproximateTimeInUs() at
|
||
|
// which point the alarm should go off.
|
||
|
// ac - the alarm which will be called.
|
||
|
void RegisterAlarmApproximateDelta(int64_t delta_in_us, AlarmCB* ac) {
|
||
|
RegisterAlarm(ApproximateNowInUsec() + delta_in_us, ac);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Unregister the alarm referred to by iterator_token; Callers should
|
||
|
// be warned that a token may have become already invalid when OnAlarm()
|
||
|
// is called, was unregistered, or OnShutdown was called on that alarm.
|
||
|
// Args:
|
||
|
// iterator_token - iterator to the alarm callback to unregister.
|
||
|
virtual void UnregisterAlarm(
|
||
|
const EpollServer::AlarmRegToken& iterator_token);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// returns the number of file-descriptors registered in this EpollServer.
|
||
|
// Returns:
|
||
|
// number of FDs registered (discounting the internal pipe used for Wake)
|
||
|
virtual int NumFDsRegistered() const;
|
||
|
|
||
|
// Summary:
|
||
|
// Force the epoll server to wake up (by writing to an internal pipe).
|
||
|
virtual void Wake();
|
||
|
|
||
|
// Summary:
|
||
|
// Wrapper around WallTimer's NowInUsec. We do this so that we can test
|
||
|
// EpollServer without using the system clock (and can avoid the flakiness
|
||
|
// that would ensue)
|
||
|
// Returns:
|
||
|
// the current time as number of microseconds since the Unix epoch.
|
||
|
virtual int64_t NowInUsec() const;
|
||
|
|
||
|
// Summary:
|
||
|
// Since calling NowInUsec() many thousands of times per
|
||
|
// WaitForEventsAndExecuteCallbacks function call is, to say the least,
|
||
|
// inefficient, we allow users to use an approximate time instead. The
|
||
|
// time returned from this function is as accurate as NowInUsec() when
|
||
|
// WaitForEventsAndExecuteCallbacks is not an ancestor of the caller's
|
||
|
// callstack.
|
||
|
// However, when WaitForEventsAndExecuteCallbacks -is- an ancestor, then
|
||
|
// this function returns the time at which the
|
||
|
// WaitForEventsAndExecuteCallbacks function started to process events or
|
||
|
// alarms.
|
||
|
//
|
||
|
// Essentially, this function makes available a fast and mostly accurate
|
||
|
// mechanism for getting the time for any function handling an event or
|
||
|
// alarm. When functions which are not handling callbacks or alarms call
|
||
|
// this function, they get the slow and "absolutely" accurate time.
|
||
|
//
|
||
|
// Users should be encouraged to use this function.
|
||
|
// Returns:
|
||
|
// the "approximate" current time as number of microseconds since the Unix
|
||
|
// epoch.
|
||
|
virtual int64_t ApproximateNowInUsec() const;
|
||
|
|
||
|
static std::string EventMaskToString(int event_mask);
|
||
|
|
||
|
// Summary:
|
||
|
// Logs the state of the epoll server with LOG(ERROR).
|
||
|
void LogStateOnCrash();
|
||
|
|
||
|
// Summary:
|
||
|
// Set the timeout to the value specified.
|
||
|
// If the timeout is set to a negative number,
|
||
|
// WaitForEventsAndExecuteCallbacks() will only return when an event has
|
||
|
// occured
|
||
|
// If the timeout is set to zero,
|
||
|
// WaitForEventsAndExecuteCallbacks() will return immediately
|
||
|
// If the timeout is set to a positive number,
|
||
|
// WaitForEventsAndExecuteCallbacks() will return when an event has
|
||
|
// occured, or when timeout_in_us microseconds has elapsed, whichever
|
||
|
// is first.
|
||
|
// Args:
|
||
|
// timeout_in_us - value specified depending on behaviour desired.
|
||
|
// See above.
|
||
|
void set_timeout_in_us(int64_t timeout_in_us) {
|
||
|
timeout_in_us_ = timeout_in_us;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Accessor for the current value of timeout_in_us.
|
||
|
int timeout_in_us() const { return timeout_in_us_; }
|
||
|
|
||
|
// Summary:
|
||
|
// Returns true when the EpollServer() is being destroyed.
|
||
|
bool in_shutdown() const { return in_shutdown_; }
|
||
|
|
||
|
protected:
|
||
|
virtual void SetNonblocking(int fd);
|
||
|
|
||
|
// This exists here so that we can override this function in unittests
|
||
|
// in order to make effective mock EpollServer objects.
|
||
|
virtual int epoll_wait_impl(int epfd,
|
||
|
struct epoll_event* events,
|
||
|
int max_events,
|
||
|
int timeout_in_ms);
|
||
|
|
||
|
// this struct is used internally, and is never used by anything external
|
||
|
// to this class. Some of its members are declared mutable to get around the
|
||
|
// restriction imposed by hash_set. Since hash_set knows nothing about the
|
||
|
// objects it stores, it has to assume that every bit of the object is used
|
||
|
// in the hash function and equal_to comparison. Thus hash_set::iterator is a
|
||
|
// const iterator. In this case, the only thing that must stay constant is
|
||
|
// fd. Everything else are just along for the ride and changing them doesn't
|
||
|
// compromise the hash_set integrity.
|
||
|
struct CBAndEventMask {
|
||
|
CBAndEventMask()
|
||
|
: cb(NULL),
|
||
|
fd(-1),
|
||
|
event_mask(0),
|
||
|
events_asserted(0),
|
||
|
events_to_fake(0),
|
||
|
in_use(false) {
|
||
|
entry.le_next = NULL;
|
||
|
entry.le_prev = NULL;
|
||
|
}
|
||
|
|
||
|
CBAndEventMask(EpollCallbackInterface* cb,
|
||
|
int event_mask,
|
||
|
int fd)
|
||
|
: cb(cb), fd(fd), event_mask(event_mask), events_asserted(0),
|
||
|
events_to_fake(0), in_use(false) {
|
||
|
entry.le_next = NULL;
|
||
|
entry.le_prev = NULL;
|
||
|
}
|
||
|
|
||
|
// Required operator for hash_set. Normally operator== should be a free
|
||
|
// standing function. However, since CBAndEventMask is a protected type and
|
||
|
// it will never be a base class, it makes no difference.
|
||
|
bool operator==(const CBAndEventMask& cb_and_mask) const {
|
||
|
return fd == cb_and_mask.fd;
|
||
|
}
|
||
|
// A callback. If the fd is unregistered inside the callchain of OnEvent,
|
||
|
// the cb will be set to NULL.
|
||
|
mutable EpollCallbackInterface* cb;
|
||
|
|
||
|
mutable LIST_ENTRY(CBAndEventMask) entry;
|
||
|
// file descriptor registered with the epoll server.
|
||
|
int fd;
|
||
|
// the current event_mask registered for this callback.
|
||
|
mutable int event_mask;
|
||
|
// the event_mask that was returned by epoll
|
||
|
mutable int events_asserted;
|
||
|
// the event_mask for the ready list to use to call OnEvent.
|
||
|
mutable int events_to_fake;
|
||
|
// toggle around calls to OnEvent to tell UnregisterFD to not erase the
|
||
|
// iterator because HandleEvent is using it.
|
||
|
mutable bool in_use;
|
||
|
};
|
||
|
|
||
|
// Custom hash function to be used by hash_set.
|
||
|
struct CBAndEventMaskHash {
|
||
|
size_t operator()(const CBAndEventMask& cb_and_eventmask) const {
|
||
|
return static_cast<size_t>(cb_and_eventmask.fd);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
using FDToCBMap = std::unordered_set<CBAndEventMask, CBAndEventMaskHash>;
|
||
|
|
||
|
// the following four functions are OS-specific, and are likely
|
||
|
// to be changed in a subclass if the poll/select method is changed
|
||
|
// from epoll.
|
||
|
|
||
|
// Summary:
|
||
|
// Deletes a file-descriptor from the set of FDs that should be
|
||
|
// monitored with epoll.
|
||
|
// Note that this only deals with modifying data relating -directly-
|
||
|
// with the epoll call-- it does not modify any data within the
|
||
|
// epoll_server.
|
||
|
// Args:
|
||
|
// fd - the file descriptor to-be-removed from the monitoring set
|
||
|
virtual void DelFD(int fd) const;
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Adds a file-descriptor to the set of FDs that should be
|
||
|
// monitored with epoll.
|
||
|
// Note that this only deals with modifying data relating -directly-
|
||
|
// with the epoll call.
|
||
|
// Args:
|
||
|
// fd - the file descriptor to-be-added to the monitoring set
|
||
|
// event_mask - the event mask (consisting of EPOLLIN, EPOLLOUT, etc
|
||
|
// OR'd together) which will be associated with this
|
||
|
// FD initially.
|
||
|
virtual void AddFD(int fd, int event_mask) const;
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modifies a file-descriptor in the set of FDs that should be
|
||
|
// monitored with epoll.
|
||
|
// Note that this only deals with modifying data relating -directly-
|
||
|
// with the epoll call.
|
||
|
// Args:
|
||
|
// fd - the file descriptor to-be-added to the monitoring set
|
||
|
// event_mask - the event mask (consisting of EPOLLIN, EPOLLOUT, etc
|
||
|
// OR'd together) which will be associated with this
|
||
|
// FD after this call.
|
||
|
virtual void ModFD(int fd, int event_mask) const;
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Modified the event mask associated with an FD in the set of
|
||
|
// data needed by epoll.
|
||
|
// Events are removed before they are added, thus, if ~0 is put
|
||
|
// in 'remove_event', whatever is put in 'add_event' will be
|
||
|
// the new event mask.
|
||
|
// If the file-descriptor specified is not registered in the
|
||
|
// epoll_server, then nothing happens as a result of this call.
|
||
|
// Args:
|
||
|
// fd - the file descriptor whose event mask is to be modified
|
||
|
// remove_event - the events which are to be removed from the current
|
||
|
// event_mask
|
||
|
// add_event - the events which are to be added to the current event_mask
|
||
|
//
|
||
|
//
|
||
|
virtual void ModifyFD(int fd, int remove_event, int add_event);
|
||
|
|
||
|
////////////////////////////////////////
|
||
|
|
||
|
// Summary:
|
||
|
// Waits for events, and calls HandleEvents() for each
|
||
|
// fd, event pair discovered to possibly have an event.
|
||
|
// Note that a callback (B) may get a spurious event if
|
||
|
// another callback (A) has closed a file-descriptor N, and
|
||
|
// the callback (B) has a newly opened file-descriptor, which
|
||
|
// also happens to be N.
|
||
|
virtual void WaitForEventsAndCallHandleEvents(int64_t timeout_in_us,
|
||
|
struct epoll_event events[],
|
||
|
int events_size);
|
||
|
|
||
|
// Summary:
|
||
|
// A function for implementing the ready list. It invokes OnEvent for each
|
||
|
// of the fd in the ready list, and takes care of adding them back to the
|
||
|
// ready list if the callback requests it (by checking that out_ready_mask
|
||
|
// is non-zero).
|
||
|
void CallReadyListCallbacks();
|
||
|
|
||
|
// Summary:
|
||
|
// An internal function for implementing the ready list. It adds a fd's
|
||
|
// CBAndEventMask to the ready list. If the fd is already on the ready
|
||
|
// list, it is a no-op.
|
||
|
void AddToReadyList(CBAndEventMask* cb_and_mask);
|
||
|
|
||
|
// Summary:
|
||
|
// An internal function for implementing the ready list. It remove a fd's
|
||
|
// CBAndEventMask from the ready list. If the fd is not on the ready list,
|
||
|
// it is a no-op.
|
||
|
void RemoveFromReadyList(const CBAndEventMask& cb_and_mask);
|
||
|
|
||
|
// Summary:
|
||
|
// Calls any pending alarms that should go off and reregisters them if they
|
||
|
// were recurring.
|
||
|
virtual void CallAndReregisterAlarmEvents();
|
||
|
|
||
|
// The file-descriptor created for epolling
|
||
|
int epoll_fd_;
|
||
|
|
||
|
// The mapping of file-descriptor to CBAndEventMasks
|
||
|
FDToCBMap cb_map_;
|
||
|
|
||
|
// Custom hash function to be used by hash_set.
|
||
|
struct AlarmCBHash {
|
||
|
size_t operator()(AlarmCB*const& p) const {
|
||
|
return reinterpret_cast<size_t>(p);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
// TODO(sushantj): Having this hash_set is avoidable. We currently have it
|
||
|
// only so that we can enforce stringent checks that a caller can not register
|
||
|
// the same alarm twice. One option is to have an implementation in which
|
||
|
// this hash_set is used only in the debug mode.
|
||
|
using AlarmCBMap = std::unordered_set<AlarmCB*, AlarmCBHash>;
|
||
|
AlarmCBMap all_alarms_;
|
||
|
|
||
|
TimeToAlarmCBMap alarm_map_;
|
||
|
|
||
|
// The amount of time in microseconds that we'll wait before returning
|
||
|
// from the WaitForEventsAndExecuteCallbacks() function.
|
||
|
// If this is positive, wait that many microseconds.
|
||
|
// If this is negative, wait forever, or for the first event that occurs
|
||
|
// If this is zero, never wait for an event.
|
||
|
int64_t timeout_in_us_;
|
||
|
|
||
|
// This is nonzero only after the invocation of epoll_wait_impl within
|
||
|
// WaitForEventsAndCallHandleEvents and before the function
|
||
|
// WaitForEventsAndExecuteCallbacks returns. At all other times, this is
|
||
|
// zero. This enables us to have relatively accurate time returned from the
|
||
|
// ApproximateNowInUs() function. See that function for more details.
|
||
|
int64_t recorded_now_in_us_;
|
||
|
|
||
|
// This is used to implement CallAndReregisterAlarmEvents. This stores
|
||
|
// all alarms that were reregistered because OnAlarm() returned a
|
||
|
// value > 0 and the time at which they should be executed is less that
|
||
|
// the current time. By storing such alarms in this map we ensure
|
||
|
// that while calling CallAndReregisterAlarmEvents we do not call
|
||
|
// OnAlarm on any alarm in this set. This ensures that we do not
|
||
|
// go in an infinite loop.
|
||
|
AlarmCBMap alarms_reregistered_and_should_be_skipped_;
|
||
|
|
||
|
LIST_HEAD(ReadyList, CBAndEventMask) ready_list_;
|
||
|
LIST_HEAD(TmpList, CBAndEventMask) tmp_list_;
|
||
|
int ready_list_size_;
|
||
|
// TODO(alyssar): make this into something that scales up.
|
||
|
static const int events_size_ = 256;
|
||
|
struct epoll_event events_[256];
|
||
|
|
||
|
#ifdef EPOLL_SERVER_EVENT_TRACING
|
||
|
struct EventRecorder {
|
||
|
public:
|
||
|
EventRecorder() : num_records_(0), record_threshold_(10000) {}
|
||
|
|
||
|
~EventRecorder() {
|
||
|
Clear();
|
||
|
}
|
||
|
|
||
|
// When a number of events equals the record threshold,
|
||
|
// the collected data summary for all FDs will be written
|
||
|
// to LOG(INFO). Note that this does not include the
|
||
|
// individual events (if you'reinterested in those, you'll
|
||
|
// have to get at them programmatically).
|
||
|
// After any such flushing to LOG(INFO) all events will
|
||
|
// be cleared.
|
||
|
// Note that the definition of an 'event' is a bit 'hazy',
|
||
|
// as it includes the 'Unregistration' event, and perhaps
|
||
|
// others.
|
||
|
void set_record_threshold(int64_t new_threshold) {
|
||
|
record_threshold_ = new_threshold;
|
||
|
}
|
||
|
|
||
|
void Clear() {
|
||
|
for (int i = 0; i < debug_events_.size(); ++i) {
|
||
|
delete debug_events_[i];
|
||
|
}
|
||
|
debug_events_.clear();
|
||
|
unregistered_fds_.clear();
|
||
|
event_counts_.clear();
|
||
|
}
|
||
|
|
||
|
void MaybeRecordAndClear() {
|
||
|
++num_records_;
|
||
|
if ((num_records_ > record_threshold_) &&
|
||
|
(record_threshold_ > 0)) {
|
||
|
LOG(INFO) << "\n" << *this;
|
||
|
num_records_ = 0;
|
||
|
Clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void RecordFDMaskEvent(int fd, int mask, const char* function) {
|
||
|
FDMaskOutput* fdmo = new FDMaskOutput(fd, mask, function);
|
||
|
debug_events_.push_back(fdmo);
|
||
|
MaybeRecordAndClear();
|
||
|
}
|
||
|
|
||
|
void RecordEpollWaitEvent(int timeout_in_ms,
|
||
|
int num_events_generated) {
|
||
|
EpollWaitOutput* ewo = new EpollWaitOutput(timeout_in_ms,
|
||
|
num_events_generated);
|
||
|
debug_events_.push_back(ewo);
|
||
|
MaybeRecordAndClear();
|
||
|
}
|
||
|
|
||
|
void RecordEpollEvent(int fd, int event_mask) {
|
||
|
Events& events_for_fd = event_counts_[fd];
|
||
|
events_for_fd.AssignFromMask(event_mask);
|
||
|
MaybeRecordAndClear();
|
||
|
}
|
||
|
|
||
|
friend ostream& operator<<(ostream& os, const EventRecorder& er) {
|
||
|
for (int i = 0; i < er.unregistered_fds_.size(); ++i) {
|
||
|
os << "fd: " << er.unregistered_fds_[i] << "\n";
|
||
|
os << er.unregistered_fds_[i];
|
||
|
}
|
||
|
for (EventCountsMap::const_iterator i = er.event_counts_.begin();
|
||
|
i != er.event_counts_.end();
|
||
|
++i) {
|
||
|
os << "fd: " << i->first << "\n";
|
||
|
os << i->second;
|
||
|
}
|
||
|
for (int i = 0; i < er.debug_events_.size(); ++i) {
|
||
|
os << *(er.debug_events_[i]) << "\n";
|
||
|
}
|
||
|
return os;
|
||
|
}
|
||
|
|
||
|
void RecordUnregistration(int fd) {
|
||
|
EventCountsMap::iterator i = event_counts_.find(fd);
|
||
|
if (i != event_counts_.end()) {
|
||
|
unregistered_fds_.push_back(i->second);
|
||
|
event_counts_.erase(i);
|
||
|
}
|
||
|
MaybeRecordAndClear();
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
class DebugOutput {
|
||
|
public:
|
||
|
friend ostream& operator<<(ostream& os, const DebugOutput& debug_output) {
|
||
|
debug_output.OutputToStream(os);
|
||
|
return os;
|
||
|
}
|
||
|
virtual void OutputToStream(ostream* os) const = 0;
|
||
|
virtual ~DebugOutput() {}
|
||
|
};
|
||
|
|
||
|
class FDMaskOutput : public DebugOutput {
|
||
|
public:
|
||
|
FDMaskOutput(int fd, int mask, const char* function) :
|
||
|
fd_(fd), mask_(mask), function_(function) {}
|
||
|
virtual void OutputToStream(ostream* os) const {
|
||
|
(*os) << "func: " << function_
|
||
|
<< "\tfd: " << fd_;
|
||
|
if (mask_ != 0) {
|
||
|
(*os) << "\tmask: " << EventMaskToString(mask_);
|
||
|
}
|
||
|
}
|
||
|
int fd_;
|
||
|
int mask_;
|
||
|
const char* function_;
|
||
|
};
|
||
|
|
||
|
class EpollWaitOutput : public DebugOutput {
|
||
|
public:
|
||
|
EpollWaitOutput(int timeout_in_ms,
|
||
|
int num_events_generated) :
|
||
|
timeout_in_ms_(timeout_in_ms),
|
||
|
num_events_generated_(num_events_generated) {}
|
||
|
virtual void OutputToStream(ostream* os) const {
|
||
|
(*os) << "timeout_in_ms: " << timeout_in_ms_
|
||
|
<< "\tnum_events_generated: " << num_events_generated_;
|
||
|
}
|
||
|
protected:
|
||
|
int timeout_in_ms_;
|
||
|
int num_events_generated_;
|
||
|
};
|
||
|
|
||
|
struct Events {
|
||
|
Events() :
|
||
|
epoll_in(0),
|
||
|
epoll_pri(0),
|
||
|
epoll_out(0),
|
||
|
epoll_rdnorm(0),
|
||
|
epoll_rdband(0),
|
||
|
epoll_wrnorm(0),
|
||
|
epoll_wrband(0),
|
||
|
epoll_msg(0),
|
||
|
epoll_err(0),
|
||
|
epoll_hup(0),
|
||
|
epoll_oneshot(0),
|
||
|
epoll_et(0) {}
|
||
|
|
||
|
void AssignFromMask(int event_mask) {
|
||
|
if (event_mask & EPOLLIN) ++epoll_in;
|
||
|
if (event_mask & EPOLLPRI) ++epoll_pri;
|
||
|
if (event_mask & EPOLLOUT) ++epoll_out;
|
||
|
if (event_mask & EPOLLRDNORM) ++epoll_rdnorm;
|
||
|
if (event_mask & EPOLLRDBAND) ++epoll_rdband;
|
||
|
if (event_mask & EPOLLWRNORM) ++epoll_wrnorm;
|
||
|
if (event_mask & EPOLLWRBAND) ++epoll_wrband;
|
||
|
if (event_mask & EPOLLMSG) ++epoll_msg;
|
||
|
if (event_mask & EPOLLERR) ++epoll_err;
|
||
|
if (event_mask & EPOLLHUP) ++epoll_hup;
|
||
|
if (event_mask & EPOLLONESHOT) ++epoll_oneshot;
|
||
|
if (event_mask & EPOLLET) ++epoll_et;
|
||
|
};
|
||
|
|
||
|
friend ostream& operator<<(ostream& os, const Events& ev) {
|
||
|
if (ev.epoll_in) {
|
||
|
os << "\t EPOLLIN: " << ev.epoll_in << "\n";
|
||
|
}
|
||
|
if (ev.epoll_pri) {
|
||
|
os << "\t EPOLLPRI: " << ev.epoll_pri << "\n";
|
||
|
}
|
||
|
if (ev.epoll_out) {
|
||
|
os << "\t EPOLLOUT: " << ev.epoll_out << "\n";
|
||
|
}
|
||
|
if (ev.epoll_rdnorm) {
|
||
|
os << "\t EPOLLRDNORM: " << ev.epoll_rdnorm << "\n";
|
||
|
}
|
||
|
if (ev.epoll_rdband) {
|
||
|
os << "\t EPOLLRDBAND: " << ev.epoll_rdband << "\n";
|
||
|
}
|
||
|
if (ev.epoll_wrnorm) {
|
||
|
os << "\t EPOLLWRNORM: " << ev.epoll_wrnorm << "\n";
|
||
|
}
|
||
|
if (ev.epoll_wrband) {
|
||
|
os << "\t EPOLLWRBAND: " << ev.epoll_wrband << "\n";
|
||
|
}
|
||
|
if (ev.epoll_msg) {
|
||
|
os << "\t EPOLLMSG: " << ev.epoll_msg << "\n";
|
||
|
}
|
||
|
if (ev.epoll_err) {
|
||
|
os << "\t EPOLLERR: " << ev.epoll_err << "\n";
|
||
|
}
|
||
|
if (ev.epoll_hup) {
|
||
|
os << "\t EPOLLHUP: " << ev.epoll_hup << "\n";
|
||
|
}
|
||
|
if (ev.epoll_oneshot) {
|
||
|
os << "\t EPOLLONESHOT: " << ev.epoll_oneshot << "\n";
|
||
|
}
|
||
|
if (ev.epoll_et) {
|
||
|
os << "\t EPOLLET: " << ev.epoll_et << "\n";
|
||
|
}
|
||
|
return os;
|
||
|
}
|
||
|
|
||
|
unsigned int epoll_in;
|
||
|
unsigned int epoll_pri;
|
||
|
unsigned int epoll_out;
|
||
|
unsigned int epoll_rdnorm;
|
||
|
unsigned int epoll_rdband;
|
||
|
unsigned int epoll_wrnorm;
|
||
|
unsigned int epoll_wrband;
|
||
|
unsigned int epoll_msg;
|
||
|
unsigned int epoll_err;
|
||
|
unsigned int epoll_hup;
|
||
|
unsigned int epoll_oneshot;
|
||
|
unsigned int epoll_et;
|
||
|
};
|
||
|
|
||
|
std::vector<DebugOutput*> debug_events_;
|
||
|
std::vector<Events> unregistered_fds_;
|
||
|
using EventCountsMap = std::unordered_map<int, Events>;
|
||
|
EventCountsMap event_counts_;
|
||
|
int64_t num_records_;
|
||
|
int64_t record_threshold_;
|
||
|
};
|
||
|
|
||
|
void ClearEventRecords() {
|
||
|
event_recorder_.Clear();
|
||
|
}
|
||
|
void WriteEventRecords(ostream* os) const {
|
||
|
(*os) << event_recorder_;
|
||
|
}
|
||
|
|
||
|
mutable EventRecorder event_recorder_;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
private:
|
||
|
// Helper functions used in the destructor.
|
||
|
void CleanupFDToCBMap();
|
||
|
void CleanupTimeToAlarmCBMap();
|
||
|
|
||
|
// The callback registered to the fds below. As the purpose of their
|
||
|
// registration is to wake the epoll server it just clears the pipe and
|
||
|
// returns.
|
||
|
std::unique_ptr<ReadPipeCallback> wake_cb_;
|
||
|
|
||
|
// A pipe owned by the epoll server. The server will be registered to listen
|
||
|
// on read_fd_ and can be woken by Wake() which writes to write_fd_.
|
||
|
int read_fd_;
|
||
|
int write_fd_;
|
||
|
|
||
|
// This boolean is checked to see if it is false at the top of the
|
||
|
// WaitForEventsAndExecuteCallbacks function. If not, then it either returns
|
||
|
// without doing work, and logs to ERROR, or aborts the program (in
|
||
|
// DEBUG mode). If so, then it sets the bool to true, does work, and
|
||
|
// sets it back to false when done. This catches unwanted recursion.
|
||
|
bool in_wait_for_events_and_execute_callbacks_;
|
||
|
|
||
|
// Returns true when the EpollServer() is being destroyed.
|
||
|
bool in_shutdown_;
|
||
|
|
||
|
DISALLOW_COPY_AND_ASSIGN(EpollServer);
|
||
|
};
|
||
|
|
||
|
class EpollAlarmCallbackInterface {
|
||
|
public:
|
||
|
// Summary:
|
||
|
// Called when an alarm times out. Invalidates an AlarmRegToken.
|
||
|
// WARNING: If a token was saved to refer to an alarm callback, OnAlarm must
|
||
|
// delete it, as the reference is no longer valid.
|
||
|
// Returns:
|
||
|
// the unix time (in microseconds) at which this alarm should be signaled
|
||
|
// again, or 0 if the alarm should be removed.
|
||
|
virtual int64_t OnAlarm() = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called when the an alarm is registered. Invalidates an AlarmRegToken.
|
||
|
// Args:
|
||
|
// token: the iterator to the the alarm registered in the alarm map.
|
||
|
// WARNING: this token becomes invalid when the alarm fires, is
|
||
|
// unregistered, or OnShutdown is called on that alarm.
|
||
|
// eps: the epoll server the alarm is registered with.
|
||
|
virtual void OnRegistration(const EpollServer::AlarmRegToken& token,
|
||
|
EpollServer* eps) = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called when the an alarm is unregistered.
|
||
|
// WARNING: It is not valid to unregister a callback and then use the token
|
||
|
// that was saved to refer to the callback.
|
||
|
virtual void OnUnregistration() = 0;
|
||
|
|
||
|
// Summary:
|
||
|
// Called when the epoll server is shutting down.
|
||
|
// Invalidates the AlarmRegToken that was given when this alarm was
|
||
|
// registered.
|
||
|
virtual void OnShutdown(EpollServer* eps) = 0;
|
||
|
|
||
|
virtual ~EpollAlarmCallbackInterface() {}
|
||
|
|
||
|
protected:
|
||
|
EpollAlarmCallbackInterface() {}
|
||
|
};
|
||
|
|
||
|
// A simple alarm which unregisters itself on destruction.
|
||
|
//
|
||
|
// PLEASE NOTE:
|
||
|
// Any classes overriding these functions must either call the implementation
|
||
|
// of the parent class, or is must otherwise make sure that the 'registered_'
|
||
|
// boolean and the token, 'token_', are updated appropriately.
|
||
|
class EpollAlarm : public EpollAlarmCallbackInterface {
|
||
|
public:
|
||
|
EpollAlarm();
|
||
|
|
||
|
~EpollAlarm() override;
|
||
|
|
||
|
// Marks the alarm as unregistered and returns 0. The return value may be
|
||
|
// safely ignored by subclasses.
|
||
|
int64_t OnAlarm() override;
|
||
|
|
||
|
// Marks the alarm as registered, and stores the token.
|
||
|
void OnRegistration(const EpollServer::AlarmRegToken& token,
|
||
|
EpollServer* eps) override;
|
||
|
|
||
|
// Marks the alarm as unregistered.
|
||
|
void OnUnregistration() override;
|
||
|
|
||
|
// Marks the alarm as unregistered.
|
||
|
void OnShutdown(EpollServer* eps) override;
|
||
|
|
||
|
// If the alarm was registered, unregister it.
|
||
|
void UnregisterIfRegistered();
|
||
|
|
||
|
bool registered() const { return registered_; }
|
||
|
|
||
|
const EpollServer* eps() const { return eps_; }
|
||
|
|
||
|
private:
|
||
|
EpollServer::AlarmRegToken token_;
|
||
|
EpollServer* eps_;
|
||
|
bool registered_;
|
||
|
};
|
||
|
|
||
|
} // namespace net
|
||
|
|
||
|
#endif // NET_TOOLS_EPOLL_SERVER_EPOLL_SERVER_H_
|