MXS-1754 Add Worker::Timer class
Worker::Timer class and Worker::DelegatingTimer templates are timers built on top of timerfd_create(2). As such they consume descriptor and hence cannot be created independently for each timer need. Each Worker has now a private timer member variable on top of which a general timer mechanism will be provided.
This commit is contained in:
		@ -422,6 +422,59 @@ private:
 | 
				
			|||||||
    Average1      m_load_1_second;  /*< The load during the last 1-second period. */
 | 
					    Average1      m_load_1_second;  /*< The load during the last 1-second period. */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * WorkerTimer is a timer class built on top of timerfd_create(2),
 | 
				
			||||||
 | 
					 * which means that each WorkerTimer instance will consume one file
 | 
				
			||||||
 | 
					 * descriptor. The implication of that is that there should not be
 | 
				
			||||||
 | 
					 * too many WorkerTimer instances. In order to be used, a WorkerTimer
 | 
				
			||||||
 | 
					 * needs a Worker instance in whose context the timer is triggered.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class WorkerTimer : private MXS_POLL_DATA
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    WorkerTimer(const WorkerTimer&) = delete;
 | 
				
			||||||
 | 
					    WorkerTimer& operator = (const WorkerTimer&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    ~WorkerTimer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Start the timer.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param internal The initial delay before the timer is
 | 
				
			||||||
 | 
					     *                 triggered, and the subsequent interval
 | 
				
			||||||
 | 
					     *                 between triggers.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @attention A value of 0 means that the timer is cancelled.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void start(uint64_t interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Cancel the timer.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void cancel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Constructor
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param pWorker  The worker in whose context the timer is to run.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    WorkerTimer(Worker* pWorker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Called when the timer is triggered.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    virtual void tick() = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    uint32_t handle(int wid, uint32_t events);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static uint32_t handler(MXS_POLL_DATA* pThis, int wid, uint32_t events);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    int     m_fd;      /**< The timerfd descriptor. */
 | 
				
			||||||
 | 
					    Worker* m_pWorker; /**< The worker in whose context the timer runs. */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Worker : public MXS_WORKER
 | 
					class Worker : public MXS_WORKER
 | 
				
			||||||
             , private MessageQueue::Handler
 | 
					             , private MessageQueue::Handler
 | 
				
			||||||
@ -434,6 +487,46 @@ public:
 | 
				
			|||||||
    typedef WorkerTask            Task;
 | 
					    typedef WorkerTask            Task;
 | 
				
			||||||
    typedef WorkerDisposableTask  DisposableTask;
 | 
					    typedef WorkerDisposableTask  DisposableTask;
 | 
				
			||||||
    typedef WorkerLoad            Load;
 | 
					    typedef WorkerLoad            Load;
 | 
				
			||||||
 | 
					    typedef WorkerTimer           Timer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * A delegating timer that delegates the timer tick handling
 | 
				
			||||||
 | 
					     * to another object.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    template<class T>
 | 
				
			||||||
 | 
					    class DelegatingTimer : public Timer
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        DelegatingTimer(const DelegatingTimer&) = delete;
 | 
				
			||||||
 | 
					        DelegatingTimer& operator = (const DelegatingTimer&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        typedef void (T::*PMethod)();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @brief Constructor
 | 
				
			||||||
 | 
					         *
 | 
				
			||||||
 | 
					         * @param pWorker     The worker in whose context the timer runs.
 | 
				
			||||||
 | 
					         * @param pDelegatee  The object to whom the timer tick is delivered.
 | 
				
			||||||
 | 
					         * @param pMethod     The method to call on @c pDelegatee when the
 | 
				
			||||||
 | 
					         *                    timer is triggered.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        DelegatingTimer(Worker* pWorker, T* pDelegatee, PMethod pMethod)
 | 
				
			||||||
 | 
					            : Timer(pWorker)
 | 
				
			||||||
 | 
					            , m_pDelegatee(pDelegatee)
 | 
				
			||||||
 | 
					            , m_pMethod(pMethod)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        void tick() /* final */
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            (m_pDelegatee->*m_pMethod)();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        T*      m_pDelegatee;
 | 
				
			||||||
 | 
					        PMethod m_pMethod;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    enum state_t
 | 
					    enum state_t
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -847,7 +940,11 @@ private:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void poll_waitevents();
 | 
					    void poll_waitevents();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void tick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
 | 
					    typedef DelegatingTimer<Worker> PrivateTimer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    STATISTICS    m_statistics;           /*< Worker statistics. */
 | 
					    STATISTICS    m_statistics;           /*< Worker statistics. */
 | 
				
			||||||
    MessageQueue* m_pQueue;               /*< The message queue of the worker. */
 | 
					    MessageQueue* m_pQueue;               /*< The message queue of the worker. */
 | 
				
			||||||
    THREAD        m_thread;               /*< The thread handle of the worker. */
 | 
					    THREAD        m_thread;               /*< The thread handle of the worker. */
 | 
				
			||||||
@ -856,7 +953,8 @@ private:
 | 
				
			|||||||
    bool          m_shutdown_initiated;   /*< Whether shutdown has been initated. */
 | 
					    bool          m_shutdown_initiated;   /*< Whether shutdown has been initated. */
 | 
				
			||||||
    uint32_t      m_nCurrent_descriptors; /*< Current number of descriptors. */
 | 
					    uint32_t      m_nCurrent_descriptors; /*< Current number of descriptors. */
 | 
				
			||||||
    uint64_t      m_nTotal_descriptors;   /*< Total number of descriptors. */
 | 
					    uint64_t      m_nTotal_descriptors;   /*< Total number of descriptors. */
 | 
				
			||||||
    Load          m_load;
 | 
					    Load          m_load;                 /*< The worker load. */
 | 
				
			||||||
 | 
					    PrivateTimer  m_timer;                /*< The worker's own timer. */
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@
 | 
				
			|||||||
#include <unistd.h>
 | 
					#include <unistd.h>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <sys/timerfd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <maxscale/alloc.h>
 | 
					#include <maxscale/alloc.h>
 | 
				
			||||||
#include <maxscale/atomic.h>
 | 
					#include <maxscale/atomic.h>
 | 
				
			||||||
@ -141,9 +142,168 @@ uint64_t WorkerLoad::get_time()
 | 
				
			|||||||
    return t.tv_sec * 1000 + (t.tv_nsec / 1000000);
 | 
					    return t.tv_sec * 1000 + (t.tv_nsec / 1000000);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int create_timerfd()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (fd == -1)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (errno == EINVAL)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Ok, we may be running on an old kernel, let's try again but without flags.
 | 
				
			||||||
 | 
					            fd = timerfd_create(CLOCK_MONOTONIC, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (fd != -1)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                int flags = fcntl(fd, F_GETFL, 0);
 | 
				
			||||||
 | 
					                if (flags != -1)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    flags |= O_NONBLOCK;
 | 
				
			||||||
 | 
					                    if (fcntl(fd, F_SETFL, flags) == -1)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        MXS_ALERT("Could not make timer fd non-blocking, MaxScale will not work: %s",
 | 
				
			||||||
 | 
					                                  mxs_strerror(errno));
 | 
				
			||||||
 | 
					                        close(fd);
 | 
				
			||||||
 | 
					                        fd = -1;
 | 
				
			||||||
 | 
					                        ss_dassert(!true);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    MXS_ALERT("Could not get timer fd flags, MaxScale will not work: %s",
 | 
				
			||||||
 | 
					                              mxs_strerror(errno));
 | 
				
			||||||
 | 
					                    close(fd);
 | 
				
			||||||
 | 
					                    fd = -1;
 | 
				
			||||||
 | 
					                    ss_dassert(!true);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                MXS_ALERT("Could not create timer file descriptor even with no flags, MaxScale "
 | 
				
			||||||
 | 
					                          "will not work: %s", mxs_strerror(errno));
 | 
				
			||||||
 | 
					                ss_dassert(!true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MXS_ALERT("Could not create timer file descriptor, MaxScale will not work: %s",
 | 
				
			||||||
 | 
					                      mxs_strerror(errno));
 | 
				
			||||||
 | 
					            ss_dassert(!true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return fd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WorkerTimer::WorkerTimer(Worker* pWorker)
 | 
				
			||||||
 | 
					    : m_fd(create_timerfd())
 | 
				
			||||||
 | 
					    , m_pWorker(pWorker)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    MXS_POLL_DATA::handler = handler;
 | 
				
			||||||
 | 
					    MXS_POLL_DATA::thread.id = m_pWorker->id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (m_fd != -1)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!m_pWorker->add_fd(m_fd, EPOLLIN, this))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MXS_ALERT("Could not add timer descriptor to worker, MaxScale will not work.");
 | 
				
			||||||
 | 
					            ::close(m_fd);
 | 
				
			||||||
 | 
					            m_fd = -1;
 | 
				
			||||||
 | 
					            ss_dassert(!true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WorkerTimer::~WorkerTimer()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (m_fd != -1)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!m_pWorker->remove_fd(m_fd))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            MXS_ERROR("Could not remove timer fd from worker.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ::close(m_fd);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void WorkerTimer::start(uint64_t interval)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // TODO: Add possibility to set initial delay and interval.
 | 
				
			||||||
 | 
					    time_t initial_sec = interval / 1000;
 | 
				
			||||||
 | 
					    long initial_nsec = (interval - initial_sec * 1000) * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    time_t interval_sec = (interval / 1000);
 | 
				
			||||||
 | 
					    long interval_nsec = (interval - interval_sec * 1000) * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct itimerspec time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    time.it_value.tv_sec = initial_sec;
 | 
				
			||||||
 | 
					    time.it_value.tv_nsec = initial_nsec;
 | 
				
			||||||
 | 
					    time.it_interval.tv_sec = interval_sec;
 | 
				
			||||||
 | 
					    time.it_interval.tv_nsec = interval_nsec;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (timerfd_settime(m_fd, 0, &time, NULL) != 0)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        MXS_ERROR("Could not set timer settings.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void WorkerTimer::cancel()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    start(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint32_t WorkerTimer::handle(int wid, uint32_t events)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ss_dassert(wid == m_pWorker->id());
 | 
				
			||||||
 | 
					    ss_dassert(events & EPOLLIN);
 | 
				
			||||||
 | 
					    ss_dassert((events & ~EPOLLIN) == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Read all events
 | 
				
			||||||
 | 
					    uint64_t expirations;
 | 
				
			||||||
 | 
					    while (read(m_fd, &expirations, sizeof(expirations)) == 0)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return MXS_POLL_READ;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//static
 | 
				
			||||||
 | 
					uint32_t WorkerTimer::handler(MXS_POLL_DATA* pThis, int wid, uint32_t events)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return static_cast<WorkerTimer*>(pThis)->handle(wid, events);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int create_epoll_instance()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    int fd = ::epoll_create(MAX_EVENTS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (fd == -1)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        MXS_ALERT("Could not create epoll-instance for worker, MaxScale will not work: %s",
 | 
				
			||||||
 | 
					                  mxs_strerror(errno));
 | 
				
			||||||
 | 
					        ss_dassert(!true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return fd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Worker::Worker()
 | 
					Worker::Worker()
 | 
				
			||||||
    : m_id(next_worker_id())
 | 
					    : m_id(next_worker_id())
 | 
				
			||||||
    , m_epoll_fd(epoll_create(MAX_EVENTS))
 | 
					    , m_epoll_fd(create_epoll_instance())
 | 
				
			||||||
    , m_state(STOPPED)
 | 
					    , m_state(STOPPED)
 | 
				
			||||||
    , m_pQueue(NULL)
 | 
					    , m_pQueue(NULL)
 | 
				
			||||||
    , m_thread(0)
 | 
					    , m_thread(0)
 | 
				
			||||||
@ -152,6 +312,7 @@ Worker::Worker()
 | 
				
			|||||||
    , m_shutdown_initiated(false)
 | 
					    , m_shutdown_initiated(false)
 | 
				
			||||||
    , m_nCurrent_descriptors(0)
 | 
					    , m_nCurrent_descriptors(0)
 | 
				
			||||||
    , m_nTotal_descriptors(0)
 | 
					    , m_nTotal_descriptors(0)
 | 
				
			||||||
 | 
					    , m_timer(this, this, &Worker::tick)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (m_epoll_fd != -1)
 | 
					    if (m_epoll_fd != -1)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -161,22 +322,16 @@ Worker::Worker()
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!m_pQueue->add_to_worker(this))
 | 
					            if (!m_pQueue->add_to_worker(this))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                MXS_ALERT("Could not add message queue to worker. MaxScale will not work.");
 | 
					                MXS_ALERT("Could not add message queue to worker, MaxScale will not work.");
 | 
				
			||||||
                ss_dassert(!true);
 | 
					                ss_dassert(!true);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            MXS_ALERT("Could not create message queue for worker. MaxScale will not work.");
 | 
					            MXS_ALERT("Could not create message queue for worker, MaxScale will not work.");
 | 
				
			||||||
            ss_dassert(!true);
 | 
					            ss_dassert(!true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        MXS_ALERT("Could not create epoll-instance for worker: %s. MaxScale will not work.",
 | 
					 | 
				
			||||||
                  mxs_strerror(errno));
 | 
					 | 
				
			||||||
        ss_dassert(!true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this_unit.ppWorkers[m_id] = this;
 | 
					    this_unit.ppWorkers[m_id] = this;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -953,6 +1108,12 @@ void Worker::poll_waitevents()
 | 
				
			|||||||
    m_state = STOPPED;
 | 
					    m_state = STOPPED;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Worker::tick()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // TODO: Add timer management here once function for adding delayed calls
 | 
				
			||||||
 | 
					    // TODO: to Worker has been added.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user