mirror of
https://github.com/ossrs/srs.git
synced 2025-02-15 04:42:04 +00:00
fix https://github.com/ossrs/srs/issues/3155 Build srt-1-fit fails with `standard attributes in middle of decl-specifiers` on GCC 12,Arch Linux. See https://github.com/Haivision/srt/releases/tag/v1.5.3
1013 lines
33 KiB
C++
1013 lines
33 KiB
C++
/*
|
|
* SRT - Secure, Reliable, Transport
|
|
* Copyright (c) 2018 Haivision Systems Inc.
|
|
*
|
|
* 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/.
|
|
*
|
|
*/
|
|
|
|
/*****************************************************************************
|
|
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
* Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the
|
|
above copyright notice, this list of conditions
|
|
and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the University of Illinois
|
|
nor the names of its contributors may be used to
|
|
endorse or promote products derived from this
|
|
software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
written by
|
|
Yunhong Gu, last updated 01/01/2011
|
|
modified by
|
|
Haivision Systems Inc.
|
|
*****************************************************************************/
|
|
|
|
#define SRT_IMPORT_EVENT
|
|
#include "platform_sys.h"
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
|
|
#if defined(__FreeBSD_kernel__)
|
|
#include <sys/event.h>
|
|
#endif
|
|
|
|
#include "common.h"
|
|
#include "epoll.h"
|
|
#include "logging.h"
|
|
#include "udt.h"
|
|
#include "utilities.h"
|
|
|
|
using namespace std;
|
|
using namespace srt::sync;
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
namespace srt {
|
|
static ostream& PrintEpollEvent(ostream& os, int events, int et_events = 0);
|
|
}
|
|
#endif
|
|
|
|
namespace srt_logging
|
|
{
|
|
extern Logger eilog, ealog;
|
|
}
|
|
|
|
using namespace srt_logging;
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
#define IF_DIRNAME(tested, flag, name) (tested & flag ? name : "")
|
|
#endif
|
|
|
|
srt::CEPoll::CEPoll():
|
|
m_iIDSeed(0)
|
|
{
|
|
// Exception -> CUDTUnited ctor.
|
|
setupMutex(m_EPollLock, "EPoll");
|
|
}
|
|
|
|
srt::CEPoll::~CEPoll()
|
|
{
|
|
releaseMutex(m_EPollLock);
|
|
}
|
|
|
|
int srt::CEPoll::create(CEPollDesc** pout)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
|
|
if (++ m_iIDSeed >= 0x7FFFFFFF)
|
|
m_iIDSeed = 0;
|
|
|
|
// Check if an item already exists. Should not ever happen.
|
|
if (m_mPolls.find(m_iIDSeed) != m_mPolls.end())
|
|
throw CUDTException(MJ_SETUP, MN_NONE);
|
|
|
|
int localid = 0;
|
|
|
|
#ifdef LINUX
|
|
|
|
// NOTE: epoll_create1() and EPOLL_CLOEXEC were introduced in GLIBC-2.9.
|
|
// So earlier versions of GLIBC, must use epoll_create() and set
|
|
// FD_CLOEXEC on the file descriptor returned by it after the fact.
|
|
#if defined(EPOLL_CLOEXEC)
|
|
int flags = 0;
|
|
#if ENABLE_SOCK_CLOEXEC
|
|
flags |= EPOLL_CLOEXEC;
|
|
#endif
|
|
localid = epoll_create1(flags);
|
|
#else
|
|
localid = epoll_create(1);
|
|
#if ENABLE_SOCK_CLOEXEC
|
|
if (localid != -1)
|
|
{
|
|
int fdFlags = fcntl(localid, F_GETFD);
|
|
if (fdFlags != -1)
|
|
{
|
|
fdFlags |= FD_CLOEXEC;
|
|
fcntl(localid, F_SETFD, fdFlags);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/* Possible reasons of -1 error:
|
|
EMFILE: The per-user limit on the number of epoll instances imposed by /proc/sys/fs/epoll/max_user_instances was encountered.
|
|
ENFILE: The system limit on the total number of open files has been reached.
|
|
ENOMEM: There was insufficient memory to create the kernel object.
|
|
*/
|
|
if (localid < 0)
|
|
throw CUDTException(MJ_SETUP, MN_NONE, errno);
|
|
#elif defined(BSD) || TARGET_OS_MAC
|
|
localid = kqueue();
|
|
if (localid < 0)
|
|
throw CUDTException(MJ_SETUP, MN_NONE, errno);
|
|
#else
|
|
// TODO: Solaris, use port_getn()
|
|
// https://docs.oracle.com/cd/E86824_01/html/E54766/port-get-3c.html
|
|
// on Windows, select
|
|
#endif
|
|
|
|
pair<map<int, CEPollDesc>::iterator, bool> res = m_mPolls.insert(make_pair(m_iIDSeed, CEPollDesc(m_iIDSeed, localid)));
|
|
if (!res.second) // Insertion failed (no memory?)
|
|
throw CUDTException(MJ_SETUP, MN_NONE);
|
|
if (pout)
|
|
*pout = &res.first->second;
|
|
|
|
return m_iIDSeed;
|
|
}
|
|
|
|
int srt::CEPoll::clear_usocks(int eid)
|
|
{
|
|
// This should remove all SRT sockets from given eid.
|
|
ScopedLock pg (m_EPollLock);
|
|
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
|
|
CEPollDesc& d = p->second;
|
|
|
|
d.clearAll();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void srt::CEPoll::clear_ready_usocks(CEPollDesc& d, int direction)
|
|
{
|
|
if ((direction & ~SRT_EPOLL_EVENTTYPES) != 0)
|
|
{
|
|
// This is internal function, so simply report an IPE on incorrect usage.
|
|
LOGC(eilog.Error, log << "CEPoll::clear_ready_usocks: IPE, event flags exceed event types: " << direction);
|
|
return;
|
|
}
|
|
ScopedLock pg (m_EPollLock);
|
|
|
|
vector<SRTSOCKET> cleared;
|
|
|
|
CEPollDesc::enotice_t::iterator i = d.enotice_begin();
|
|
while (i != d.enotice_end())
|
|
{
|
|
IF_HEAVY_LOGGING(SRTSOCKET subsock = i->fd);
|
|
SRTSOCKET rs = d.clearEventSub(i++, direction);
|
|
// This function returns:
|
|
// - a valid socket - if there are no other subscription after 'direction' was cleared
|
|
// - SRT_INVALID_SOCK otherwise
|
|
// Valid sockets should be collected as sockets that no longer
|
|
// have a subscribed event should be deleted from subscriptions.
|
|
if (rs != SRT_INVALID_SOCK)
|
|
{
|
|
HLOGC(eilog.Debug, log << "CEPoll::clear_ready_usocks: @" << rs << " got all subscription cleared");
|
|
cleared.push_back(rs);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(eilog.Debug, log << "CEPoll::clear_ready_usocks: @" << subsock << " is still subscribed");
|
|
}
|
|
}
|
|
|
|
for (size_t j = 0; j < cleared.size(); ++j)
|
|
d.removeSubscription(cleared[j]);
|
|
}
|
|
|
|
int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
|
|
#ifdef LINUX
|
|
epoll_event ev;
|
|
memset(&ev, 0, sizeof(epoll_event));
|
|
|
|
if (NULL == events)
|
|
ev.events = EPOLLIN | EPOLLOUT | EPOLLERR;
|
|
else
|
|
{
|
|
ev.events = 0;
|
|
if (*events & SRT_EPOLL_IN)
|
|
ev.events |= EPOLLIN;
|
|
if (*events & SRT_EPOLL_OUT)
|
|
ev.events |= EPOLLOUT;
|
|
if (*events & SRT_EPOLL_ERR)
|
|
ev.events |= EPOLLERR;
|
|
}
|
|
|
|
ev.data.fd = s;
|
|
if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_ADD, s, &ev) < 0)
|
|
throw CUDTException();
|
|
#elif defined(BSD) || TARGET_OS_MAC
|
|
struct kevent ke[2];
|
|
int num = 0;
|
|
|
|
if (NULL == events)
|
|
{
|
|
EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
|
EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (*events & SRT_EPOLL_IN)
|
|
{
|
|
EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
|
}
|
|
if (*events & SRT_EPOLL_OUT)
|
|
{
|
|
EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
|
|
}
|
|
}
|
|
if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0)
|
|
throw CUDTException();
|
|
#else
|
|
|
|
// fake use 'events' to prevent warning. Remove when implemented.
|
|
(void)events;
|
|
(void)s;
|
|
|
|
#ifdef _MSC_VER
|
|
// Microsoft Visual Studio doesn't support the #warning directive - nonstandard anyway.
|
|
// Use #pragma message with the same text.
|
|
// All other compilers should be ok :)
|
|
#pragma message("WARNING: Unsupported system for epoll. The epoll_add_ssock() API call won't work on this platform.")
|
|
#else
|
|
#warning "Unsupported system for epoll. The epoll_add_ssock() API call won't work on this platform."
|
|
#endif
|
|
|
|
#endif
|
|
|
|
p->second.m_sLocals.insert(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
|
|
#ifdef LINUX
|
|
epoll_event ev; // ev is ignored, for compatibility with old Linux kernel only.
|
|
if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_DEL, s, &ev) < 0)
|
|
throw CUDTException();
|
|
#elif defined(BSD) || TARGET_OS_MAC
|
|
struct kevent ke;
|
|
|
|
//
|
|
// Since I don't know what was set before
|
|
// Just clear out both read and write
|
|
//
|
|
EV_SET(&ke, s, EVFILT_READ, EV_DELETE, 0, 0, NULL);
|
|
kevent(p->second.m_iLocalID, &ke, 1, NULL, 0, NULL);
|
|
EV_SET(&ke, s, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
|
|
kevent(p->second.m_iLocalID, &ke, 1, NULL, 0, NULL);
|
|
#endif
|
|
|
|
p->second.m_sLocals.erase(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Need this to atomically modify polled events (ex: remove write/keep read)
|
|
int srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
IF_HEAVY_LOGGING(ostringstream evd);
|
|
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
|
|
CEPollDesc& d = p->second;
|
|
|
|
int32_t evts = events ? *events : uint32_t(SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR);
|
|
bool edgeTriggered = evts & SRT_EPOLL_ET;
|
|
evts &= ~SRT_EPOLL_ET;
|
|
|
|
// et_evts = all events, if SRT_EPOLL_ET, or only those that are always ET otherwise.
|
|
int32_t et_evts = edgeTriggered ? evts : evts & SRT_EPOLL_ETONLY;
|
|
if (evts)
|
|
{
|
|
pair<CEPollDesc::ewatch_t::iterator, bool> iter_new = d.addWatch(u, evts, et_evts);
|
|
CEPollDesc::Wait& wait = iter_new.first->second;
|
|
if (!iter_new.second)
|
|
{
|
|
// The object exists. We only are certain about the `u`
|
|
// parameter, but others are probably unchanged. Change them
|
|
// forcefully and take out notices that are no longer valid.
|
|
const int removable = wait.watch & ~evts;
|
|
IF_HEAVY_LOGGING(PrintEpollEvent(evd, evts & (~wait.watch)));
|
|
|
|
// Check if there are any events that would be removed.
|
|
// If there are no removed events watched (for example, when
|
|
// only new events are being added to existing socket),
|
|
// there's nothing to remove, but might be something to update.
|
|
if (removable)
|
|
{
|
|
d.removeExcessEvents(wait, evts);
|
|
}
|
|
|
|
// Update the watch configuration, including edge
|
|
wait.watch = evts;
|
|
wait.edge = et_evts;
|
|
|
|
// Now it should look exactly like newly added
|
|
// and the state is also updated
|
|
HLOGC(ealog.Debug, log << "srt_epoll_update_usock: UPDATED E" << eid << " for @" << u << " +" << evd.str());
|
|
}
|
|
else
|
|
{
|
|
IF_HEAVY_LOGGING(PrintEpollEvent(evd, evts));
|
|
HLOGC(ealog.Debug, log << "srt_epoll_update_usock: ADDED E" << eid << " for @" << u << " " << evd.str());
|
|
}
|
|
|
|
const int newstate = wait.watch & wait.state;
|
|
if (newstate)
|
|
{
|
|
d.addEventNotice(wait, u, newstate);
|
|
}
|
|
}
|
|
else if (edgeTriggered)
|
|
{
|
|
LOGC(ealog.Error, log << "srt_epoll_update_usock: Specified only SRT_EPOLL_ET flag, but no event flag. Error.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
else
|
|
{
|
|
// Update with no events means to remove subscription
|
|
HLOGC(ealog.Debug, log << "srt_epoll_update_usock: REMOVED E" << eid << " socket @" << u);
|
|
d.removeSubscription(u);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
|
|
#ifdef LINUX
|
|
epoll_event ev;
|
|
memset(&ev, 0, sizeof(epoll_event));
|
|
|
|
if (NULL == events)
|
|
ev.events = EPOLLIN | EPOLLOUT | EPOLLERR;
|
|
else
|
|
{
|
|
ev.events = 0;
|
|
if (*events & SRT_EPOLL_IN)
|
|
ev.events |= EPOLLIN;
|
|
if (*events & SRT_EPOLL_OUT)
|
|
ev.events |= EPOLLOUT;
|
|
if (*events & SRT_EPOLL_ERR)
|
|
ev.events |= EPOLLERR;
|
|
}
|
|
|
|
ev.data.fd = s;
|
|
if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_MOD, s, &ev) < 0)
|
|
throw CUDTException();
|
|
#elif defined(BSD) || TARGET_OS_MAC
|
|
struct kevent ke[2];
|
|
int num = 0;
|
|
|
|
//
|
|
// Since I don't know what was set before
|
|
// Just clear out both read and write
|
|
//
|
|
EV_SET(&ke[0], s, EVFILT_READ, EV_DELETE, 0, 0, NULL);
|
|
kevent(p->second.m_iLocalID, ke, 1, NULL, 0, NULL);
|
|
EV_SET(&ke[0], s, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
|
|
kevent(p->second.m_iLocalID, ke, 1, NULL, 0, NULL);
|
|
if (NULL == events)
|
|
{
|
|
EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
|
EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (*events & SRT_EPOLL_IN)
|
|
{
|
|
EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
|
}
|
|
if (*events & SRT_EPOLL_OUT)
|
|
{
|
|
EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
|
|
}
|
|
}
|
|
if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0)
|
|
throw CUDTException();
|
|
#else
|
|
|
|
// fake use 'events' to prevent warning. Remove when implemented.
|
|
(void)events;
|
|
(void)s;
|
|
|
|
#endif
|
|
// Assuming add is used if not inserted
|
|
// p->second.m_sLocals.insert(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CEPoll::setflags(const int eid, int32_t flags)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
CEPollDesc& ed = p->second;
|
|
|
|
int32_t oflags = ed.flags();
|
|
|
|
if (flags == -1)
|
|
return oflags;
|
|
|
|
if (flags == 0)
|
|
{
|
|
ed.clr_flags(~int32_t());
|
|
}
|
|
else
|
|
{
|
|
ed.set_flags(flags);
|
|
}
|
|
|
|
return oflags;
|
|
}
|
|
|
|
int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut)
|
|
{
|
|
// It is allowed to call this function witn fdsSize == 0
|
|
// and therefore also NULL fdsSet. This will then only report
|
|
// the number of ready sockets, just without information which.
|
|
if (fdsSize < 0 || (fdsSize > 0 && !fdsSet))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
|
|
steady_clock::time_point entertime = steady_clock::now();
|
|
|
|
while (true)
|
|
{
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
CEPollDesc& ed = p->second;
|
|
|
|
if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty())
|
|
{
|
|
// Empty EID is not allowed, report error.
|
|
throw CUDTException(MJ_NOTSUP, MN_EEMPTY);
|
|
}
|
|
|
|
if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK) && (fdsSet == NULL || fdsSize == 0))
|
|
{
|
|
// Empty EID is not allowed, report error.
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
if (!ed.m_sLocals.empty())
|
|
{
|
|
// XXX Add error log
|
|
// uwait should not be used with EIDs subscribed to system sockets
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
int total = 0; // This is a list, so count it during iteration
|
|
CEPollDesc::enotice_t::iterator i = ed.enotice_begin();
|
|
while (i != ed.enotice_end())
|
|
{
|
|
int pos = total; // previous past-the-end position
|
|
++total;
|
|
|
|
if (total > fdsSize)
|
|
break;
|
|
|
|
fdsSet[pos] = *i;
|
|
|
|
ed.checkEdge(i++); // NOTE: potentially deletes `i`
|
|
}
|
|
if (total)
|
|
return total;
|
|
}
|
|
|
|
if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000)))
|
|
break; // official wait does: throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
|
|
|
|
CGlobEvent::waitForEvent();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CEPoll::wait(const int eid, set<SRTSOCKET>* readfds, set<SRTSOCKET>* writefds, int64_t msTimeOut, set<SYSSOCKET>* lrfds, set<SYSSOCKET>* lwfds)
|
|
{
|
|
// if all fields is NULL and waiting time is infinite, then this would be a deadlock
|
|
if (!readfds && !writefds && !lrfds && !lwfds && (msTimeOut < 0))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
// Clear these sets in case the app forget to do it.
|
|
if (readfds) readfds->clear();
|
|
if (writefds) writefds->clear();
|
|
if (lrfds) lrfds->clear();
|
|
if (lwfds) lwfds->clear();
|
|
|
|
int total = 0;
|
|
|
|
srt::sync::steady_clock::time_point entertime = srt::sync::steady_clock::now();
|
|
while (true)
|
|
{
|
|
{
|
|
ScopedLock epollock(m_EPollLock);
|
|
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(eid);
|
|
if (p == m_mPolls.end())
|
|
{
|
|
LOGC(ealog.Error, log << "EID:" << eid << " INVALID.");
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
}
|
|
|
|
CEPollDesc& ed = p->second;
|
|
|
|
if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty() && ed.m_sLocals.empty())
|
|
{
|
|
// Empty EID is not allowed, report error.
|
|
//throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
LOGC(ealog.Error, log << "EID:" << eid << " no sockets to check, this would deadlock");
|
|
throw CUDTException(MJ_NOTSUP, MN_EEMPTY, 0);
|
|
}
|
|
|
|
if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK))
|
|
{
|
|
// Empty report is not allowed, report error.
|
|
if (!ed.m_sLocals.empty() && (!lrfds || !lwfds))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
|
|
if (!ed.watch_empty() && (!readfds || !writefds))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
IF_HEAVY_LOGGING(int total_noticed = 0);
|
|
IF_HEAVY_LOGGING(ostringstream debug_sockets);
|
|
// Sockets with exceptions are returned to both read and write sets.
|
|
for (CEPollDesc::enotice_t::iterator it = ed.enotice_begin(), it_next = it; it != ed.enotice_end(); it = it_next)
|
|
{
|
|
++it_next;
|
|
IF_HEAVY_LOGGING(++total_noticed);
|
|
if (readfds && ((it->events & SRT_EPOLL_IN) || (it->events & SRT_EPOLL_ERR)))
|
|
{
|
|
if (readfds->insert(it->fd).second)
|
|
++total;
|
|
}
|
|
|
|
if (writefds && ((it->events & SRT_EPOLL_OUT) || (it->events & SRT_EPOLL_ERR)))
|
|
{
|
|
if (writefds->insert(it->fd).second)
|
|
++total;
|
|
}
|
|
|
|
IF_HEAVY_LOGGING(debug_sockets << " " << it->fd << ":"
|
|
<< IF_DIRNAME(it->events, SRT_EPOLL_IN, "R")
|
|
<< IF_DIRNAME(it->events, SRT_EPOLL_OUT, "W")
|
|
<< IF_DIRNAME(it->events, SRT_EPOLL_ERR, "E"));
|
|
|
|
if (ed.checkEdge(it)) // NOTE: potentially erases 'it'.
|
|
{
|
|
IF_HEAVY_LOGGING(debug_sockets << "!");
|
|
}
|
|
}
|
|
|
|
HLOGC(ealog.Debug, log << "CEPoll::wait: REPORTED " << total << "/" << total_noticed
|
|
<< debug_sockets.str());
|
|
|
|
if ((lrfds || lwfds) && !ed.m_sLocals.empty())
|
|
{
|
|
#ifdef LINUX
|
|
const int max_events = ed.m_sLocals.size();
|
|
SRT_ASSERT(max_events > 0);
|
|
srt::FixedArray<epoll_event> ev(max_events);
|
|
int nfds = ::epoll_wait(ed.m_iLocalID, ev.data(), ev.size(), 0);
|
|
|
|
IF_HEAVY_LOGGING(const int prev_total = total);
|
|
for (int i = 0; i < nfds; ++ i)
|
|
{
|
|
if ((NULL != lrfds) && (ev[i].events & EPOLLIN))
|
|
{
|
|
lrfds->insert(ev[i].data.fd);
|
|
++ total;
|
|
}
|
|
if ((NULL != lwfds) && (ev[i].events & EPOLLOUT))
|
|
{
|
|
lwfds->insert(ev[i].data.fd);
|
|
++ total;
|
|
}
|
|
}
|
|
HLOGC(ealog.Debug, log << "CEPoll::wait: LINUX: picking up " << (total - prev_total) << " ready fds.");
|
|
|
|
#elif defined(BSD) || TARGET_OS_MAC
|
|
struct timespec tmout = {0, 0};
|
|
const int max_events = (int)ed.m_sLocals.size();
|
|
SRT_ASSERT(max_events > 0);
|
|
srt::FixedArray<struct kevent> ke(max_events);
|
|
|
|
int nfds = kevent(ed.m_iLocalID, NULL, 0, ke.data(), (int)ke.size(), &tmout);
|
|
IF_HEAVY_LOGGING(const int prev_total = total);
|
|
|
|
for (int i = 0; i < nfds; ++ i)
|
|
{
|
|
if ((NULL != lrfds) && (ke[i].filter == EVFILT_READ))
|
|
{
|
|
lrfds->insert((int)ke[i].ident);
|
|
++ total;
|
|
}
|
|
if ((NULL != lwfds) && (ke[i].filter == EVFILT_WRITE))
|
|
{
|
|
lwfds->insert((int)ke[i].ident);
|
|
++ total;
|
|
}
|
|
}
|
|
|
|
HLOGC(ealog.Debug, log << "CEPoll::wait: Darwin/BSD: picking up " << (total - prev_total) << " ready fds.");
|
|
|
|
#else
|
|
//currently "select" is used for all non-Linux platforms.
|
|
//faster approaches can be applied for specific systems in the future.
|
|
|
|
//"select" has a limitation on the number of sockets
|
|
int max_fd = 0;
|
|
|
|
fd_set rqreadfds;
|
|
fd_set rqwritefds;
|
|
FD_ZERO(&rqreadfds);
|
|
FD_ZERO(&rqwritefds);
|
|
|
|
for (set<SYSSOCKET>::const_iterator i = ed.m_sLocals.begin(); i != ed.m_sLocals.end(); ++ i)
|
|
{
|
|
if (lrfds)
|
|
FD_SET(*i, &rqreadfds);
|
|
if (lwfds)
|
|
FD_SET(*i, &rqwritefds);
|
|
if ((int)*i > max_fd)
|
|
max_fd = (int)*i;
|
|
}
|
|
|
|
IF_HEAVY_LOGGING(const int prev_total = total);
|
|
timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
if (::select(max_fd + 1, &rqreadfds, &rqwritefds, NULL, &tv) > 0)
|
|
{
|
|
for (set<SYSSOCKET>::const_iterator i = ed.m_sLocals.begin(); i != ed.m_sLocals.end(); ++ i)
|
|
{
|
|
if (lrfds && FD_ISSET(*i, &rqreadfds))
|
|
{
|
|
lrfds->insert(*i);
|
|
++ total;
|
|
}
|
|
if (lwfds && FD_ISSET(*i, &rqwritefds))
|
|
{
|
|
lwfds->insert(*i);
|
|
++ total;
|
|
}
|
|
}
|
|
}
|
|
|
|
HLOGC(ealog.Debug, log << "CEPoll::wait: select(otherSYS): picking up " << (total - prev_total) << " ready fds.");
|
|
#endif
|
|
}
|
|
|
|
} // END-LOCK: m_EPollLock
|
|
|
|
HLOGC(ealog.Debug, log << "CEPoll::wait: Total of " << total << " READY SOCKETS");
|
|
|
|
if (total > 0)
|
|
return total;
|
|
|
|
if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000)))
|
|
{
|
|
HLOGC(ealog.Debug, log << "EID:" << eid << ": TIMEOUT.");
|
|
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
|
|
}
|
|
|
|
const bool wait_signaled SRT_ATR_UNUSED = CGlobEvent::waitForEvent();
|
|
HLOGC(ealog.Debug, log << "CEPoll::wait: EVENT WAITING: "
|
|
<< (wait_signaled ? "TRIGGERED" : "CHECKPOINT"));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CEPoll::swait(CEPollDesc& d, map<SRTSOCKET, int>& st, int64_t msTimeOut, bool report_by_exception)
|
|
{
|
|
{
|
|
ScopedLock lg (m_EPollLock);
|
|
if (!d.flags(SRT_EPOLL_ENABLE_EMPTY) && d.watch_empty() && msTimeOut < 0)
|
|
{
|
|
// no socket is being monitored, this may be a deadlock
|
|
LOGC(ealog.Error, log << "EID:" << d.m_iID << " no sockets to check, this would deadlock");
|
|
if (report_by_exception)
|
|
throw CUDTException(MJ_NOTSUP, MN_EEMPTY, 0);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
st.clear();
|
|
|
|
steady_clock::time_point entertime = steady_clock::now();
|
|
while (true)
|
|
{
|
|
{
|
|
// Not extracting separately because this function is
|
|
// for internal use only and we state that the eid could
|
|
// not be deleted or changed the target CEPollDesc in the
|
|
// meantime.
|
|
|
|
// Here we only prevent the pollset be updated simultaneously
|
|
// with unstable reading.
|
|
ScopedLock lg (m_EPollLock);
|
|
|
|
if (!d.flags(SRT_EPOLL_ENABLE_EMPTY) && d.watch_empty())
|
|
{
|
|
// Empty EID is not allowed, report error.
|
|
throw CUDTException(MJ_NOTSUP, MN_EEMPTY);
|
|
}
|
|
|
|
if (!d.m_sLocals.empty())
|
|
{
|
|
// XXX Add error log
|
|
// uwait should not be used with EIDs subscribed to system sockets
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
bool empty = d.enotice_empty();
|
|
|
|
if (!empty || msTimeOut == 0)
|
|
{
|
|
IF_HEAVY_LOGGING(ostringstream singles);
|
|
// If msTimeOut == 0, it means that we need the information
|
|
// immediately, we don't want to wait. Therefore in this case
|
|
// report also when none is ready.
|
|
int total = 0; // This is a list, so count it during iteration
|
|
CEPollDesc::enotice_t::iterator i = d.enotice_begin();
|
|
while (i != d.enotice_end())
|
|
{
|
|
++total;
|
|
st[i->fd] = i->events;
|
|
IF_HEAVY_LOGGING(singles << "@" << i->fd << ":");
|
|
IF_HEAVY_LOGGING(PrintEpollEvent(singles, i->events, i->parent->edgeOnly()));
|
|
const bool edged SRT_ATR_UNUSED = d.checkEdge(i++); // NOTE: potentially deletes `i`
|
|
IF_HEAVY_LOGGING(singles << (edged ? "<^> " : " "));
|
|
}
|
|
|
|
// Logging into 'singles' because it notifies as to whether
|
|
// the edge-triggered event has been cleared
|
|
HLOGC(ealog.Debug, log << "E" << d.m_iID << " rdy=" << total << ": "
|
|
<< singles.str()
|
|
<< " TRACKED: " << d.DisplayEpollWatch());
|
|
return total;
|
|
}
|
|
// Don't report any updates because this check happens
|
|
// extremely often.
|
|
}
|
|
|
|
if ((msTimeOut >= 0) && ((steady_clock::now() - entertime) >= microseconds_from(msTimeOut * int64_t(1000))))
|
|
{
|
|
HLOGC(ealog.Debug, log << "EID:" << d.m_iID << ": TIMEOUT.");
|
|
if (report_by_exception)
|
|
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
|
|
return 0; // meaning "none is ready"
|
|
}
|
|
|
|
CGlobEvent::waitForEvent();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool srt::CEPoll::empty(const CEPollDesc& d) const
|
|
{
|
|
ScopedLock lg (m_EPollLock);
|
|
return d.watch_empty();
|
|
}
|
|
|
|
int srt::CEPoll::release(const int eid)
|
|
{
|
|
ScopedLock pg(m_EPollLock);
|
|
|
|
map<int, CEPollDesc>::iterator i = m_mPolls.find(eid);
|
|
if (i == m_mPolls.end())
|
|
throw CUDTException(MJ_NOTSUP, MN_EIDINVAL);
|
|
|
|
#ifdef LINUX
|
|
// release local/system epoll descriptor
|
|
::close(i->second.m_iLocalID);
|
|
#elif defined(BSD) || TARGET_OS_MAC
|
|
::close(i->second.m_iLocalID);
|
|
#endif
|
|
|
|
m_mPolls.erase(i);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set<int>& eids, const int events, const bool enable)
|
|
{
|
|
// As event flags no longer contain only event types, check now.
|
|
if ((events & ~SRT_EPOLL_EVENTTYPES) != 0)
|
|
{
|
|
LOGC(eilog.Fatal, log << "epoll/update: IPE: 'events' parameter shall not contain special flags!");
|
|
return -1; // still, ignored.
|
|
}
|
|
|
|
int nupdated = 0;
|
|
vector<int> lost;
|
|
|
|
IF_HEAVY_LOGGING(ostringstream debug);
|
|
IF_HEAVY_LOGGING(debug << "epoll/update: @" << uid << " " << (enable ? "+" : "-"));
|
|
IF_HEAVY_LOGGING(PrintEpollEvent(debug, events));
|
|
|
|
ScopedLock pg (m_EPollLock);
|
|
for (set<int>::iterator i = eids.begin(); i != eids.end(); ++ i)
|
|
{
|
|
map<int, CEPollDesc>::iterator p = m_mPolls.find(*i);
|
|
if (p == m_mPolls.end())
|
|
{
|
|
HLOGC(eilog.Note, log << "epoll/update: E" << *i << " was deleted in the meantime");
|
|
// EID invalid, though still present in the socket's subscriber list
|
|
// (dangling in the socket). Postpone to fix the subscruption and continue.
|
|
lost.push_back(*i);
|
|
continue;
|
|
}
|
|
|
|
CEPollDesc& ed = p->second;
|
|
|
|
// Check if this EID is subscribed for this socket.
|
|
CEPollDesc::Wait* pwait = ed.watch_find(uid);
|
|
if (!pwait)
|
|
{
|
|
// As this is mapped in the socket's data, it should be impossible.
|
|
LOGC(eilog.Error, log << "epoll/update: IPE: update struck E"
|
|
<< (*i) << " which is NOT SUBSCRIBED to @" << uid);
|
|
continue;
|
|
}
|
|
|
|
IF_HEAVY_LOGGING(string tracking = " TRACKING: " + ed.DisplayEpollWatch());
|
|
// compute new states
|
|
|
|
// New state to be set into the permanent state
|
|
const int newstate = enable ? pwait->state | events // SET event bits if enable
|
|
: pwait->state & (~events); // CLEAR event bits
|
|
|
|
// compute states changes!
|
|
int changes = pwait->state ^ newstate; // oldState XOR newState
|
|
if (!changes)
|
|
{
|
|
HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i)
|
|
<< tracking << " NOT updated: no changes");
|
|
continue; // no changes!
|
|
}
|
|
// assign new state
|
|
pwait->state = newstate;
|
|
// filter change relating what is watching
|
|
changes &= pwait->watch;
|
|
if (!changes)
|
|
{
|
|
HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i)
|
|
<< tracking << " NOT updated: not subscribed");
|
|
continue; // no change watching
|
|
}
|
|
// set events changes!
|
|
|
|
// This function will update the notice object associated with
|
|
// the given events, that is:
|
|
// - if enable, it will set event flags, possibly in a new notice object
|
|
// - if !enable, it will clear event flags, possibly remove notice if resulted in 0
|
|
ed.updateEventNotice(*pwait, uid, events, enable);
|
|
++nupdated;
|
|
|
|
HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i)
|
|
<< " TRACKING: " << ed.DisplayEpollWatch());
|
|
}
|
|
|
|
for (vector<int>::iterator i = lost.begin(); i != lost.end(); ++ i)
|
|
eids.erase(*i);
|
|
|
|
return nupdated;
|
|
}
|
|
|
|
// Debug use only.
|
|
#if ENABLE_HEAVY_LOGGING
|
|
namespace srt
|
|
{
|
|
|
|
static ostream& PrintEpollEvent(ostream& os, int events, int et_events)
|
|
{
|
|
static pair<int, const char*> const namemap [] = {
|
|
make_pair(SRT_EPOLL_IN, "R"),
|
|
make_pair(SRT_EPOLL_OUT, "W"),
|
|
make_pair(SRT_EPOLL_ERR, "E"),
|
|
make_pair(SRT_EPOLL_UPDATE, "U")
|
|
};
|
|
|
|
int N = Size(namemap);
|
|
|
|
for (int i = 0; i < N; ++i)
|
|
{
|
|
if (events & namemap[i].first)
|
|
{
|
|
os << "[";
|
|
if (et_events & namemap[i].first)
|
|
os << "^";
|
|
os << namemap[i].second << "]";
|
|
}
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
string DisplayEpollResults(const std::map<SRTSOCKET, int>& sockset)
|
|
{
|
|
typedef map<SRTSOCKET, int> fmap_t;
|
|
ostringstream os;
|
|
for (fmap_t::const_iterator i = sockset.begin(); i != sockset.end(); ++i)
|
|
{
|
|
os << "@" << i->first << ":";
|
|
PrintEpollEvent(os, i->second);
|
|
os << " ";
|
|
}
|
|
|
|
return os.str();
|
|
}
|
|
|
|
string CEPollDesc::DisplayEpollWatch()
|
|
{
|
|
ostringstream os;
|
|
for (ewatch_t::const_iterator i = m_USockWatchState.begin(); i != m_USockWatchState.end(); ++i)
|
|
{
|
|
os << "@" << i->first << ":";
|
|
PrintEpollEvent(os, i->second.watch, i->second.edge);
|
|
os << " ";
|
|
}
|
|
|
|
return os.str();
|
|
}
|
|
|
|
} // namespace srt
|
|
|
|
#endif
|