blob: 929e943cb3c99bebcb30faac9b8dbce2e9b7750f [file] [log] [blame]
/*
*
* Copyright (c) 2020-2021 Project CHIP Authors
* Copyright (c) 2014-2017 Nest Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements Layer using select().
*/
#include <lib/support/CodeUtils.h>
#include <lib/support/TimeUtils.h>
#include <platform/LockTracker.h>
#include <system/SystemFaultInjection.h>
#include <system/SystemLayer.h>
#include <system/SystemLayerImplSelect.h>
#include <errno.h>
#define DEFAULT_MIN_SLEEP_PERIOD (60 * 60 * 24 * 30) // Month [sec]
// Choose an approximation of PTHREAD_NULL if pthread.h doesn't define one.
#if CHIP_SYSTEM_CONFIG_POSIX_LOCKING && !defined(PTHREAD_NULL)
#define PTHREAD_NULL 0
#endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING && !defined(PTHREAD_NULL)
#if CHIP_DEVICE_CONFIG_ENABLE_MDNS && !__ZEPHYR__
namespace chip {
namespace Mdns {
void GetMdnsTimeout(timeval & timeout);
void HandleMdnsTimeout();
} // namespace Mdns
} // namespace chip
#endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS && !__ZEPHYR__
namespace chip {
namespace System {
CHIP_ERROR LayerImplSelect::Init()
{
VerifyOrReturnError(!mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
RegisterPOSIXErrorFormatter();
ReturnErrorOnFailure(mTimerList.Init());
for (auto & w : mSocketWatchPool)
{
w.Clear();
}
#if CHIP_SYSTEM_CONFIG_POSIX_LOCKING
mHandleSelectThread = PTHREAD_NULL;
#endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
// Create an event to allow an arbitrary thread to wake the thread in the select loop.
ReturnErrorOnFailure(mWakeEvent.Open(*this));
VerifyOrReturnError(mLayerState.Init(), CHIP_ERROR_INCORRECT_STATE);
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::Shutdown()
{
VerifyOrReturnError(mLayerState.Shutdown(), CHIP_ERROR_INCORRECT_STATE);
Timer * timer;
while ((timer = mTimerList.PopEarliest()) != nullptr)
{
timer->Clear();
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
if (timer->mTimerSource != nullptr)
{
dispatch_source_cancel(timer->mTimerSource);
dispatch_release(timer->mTimerSource);
}
#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
timer->Release();
}
mWakeEvent.Close(*this);
mLayerState.Reset(); // Return to uninitialized state to permit re-initialization.
return CHIP_NO_ERROR;
}
void LayerImplSelect::Signal()
{
/*
* Wake up the I/O thread by writing a single byte to the wake pipe.
*
* If this is being called from within an I/O event callback, then writing to the wake pipe can be skipped,
* since the I/O thread is already awake.
*
* Furthermore, we don't care if this write fails as the only reasonably likely failure is that the pipe is full, in which
* case the select calling thread is going to wake up anyway.
*/
#if CHIP_SYSTEM_CONFIG_POSIX_LOCKING
if (pthread_equal(mHandleSelectThread, pthread_self()))
{
return;
}
#endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
// Send notification to wake up the select call.
CHIP_ERROR status = mWakeEvent.Notify();
if (status != CHIP_NO_ERROR)
{
ChipLogError(chipSystemLayer, "System wake event notify failed: %" CHIP_ERROR_FORMAT, status.Format());
}
}
CHIP_ERROR LayerImplSelect::StartTimer(uint32_t delayMilliseconds, TimerCompleteCallback onComplete, void * appState)
{
VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
CHIP_SYSTEM_FAULT_INJECT(FaultInjection::kFault_TimeoutImmediate, delayMilliseconds = 0);
CancelTimer(onComplete, appState);
Timer * timer = Timer::New(*this, delayMilliseconds, onComplete, appState);
VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
dispatch_queue_t dispatchQueue = GetDispatchQueue();
if (dispatchQueue)
{
(void) mTimerList.Add(timer);
dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatchQueue);
if (timerSource == nullptr)
{
chipDie();
}
timer->mTimerSource = timerSource;
dispatch_source_set_timer(timerSource, dispatch_walltime(NULL, delayMilliseconds * NSEC_PER_MSEC), 0, 100 * NSEC_PER_MSEC);
dispatch_source_set_event_handler(timerSource, ^{
dispatch_source_cancel(timerSource);
dispatch_release(timerSource);
this->HandleTimerComplete(timer);
});
dispatch_resume(timerSource);
return CHIP_NO_ERROR;
}
#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
if (mTimerList.Add(timer) == timer)
{
// The new timer is the earliest, so the time until the next event has probably changed.
Signal();
}
return CHIP_NO_ERROR;
}
void LayerImplSelect::CancelTimer(TimerCompleteCallback onComplete, void * appState)
{
VerifyOrReturn(mLayerState.IsInitialized());
Timer * timer = mTimerList.Remove(onComplete, appState);
VerifyOrReturn(timer != nullptr);
timer->Clear();
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
if (timer->mTimerSource != nullptr)
{
dispatch_source_cancel(timer->mTimerSource);
dispatch_release(timer->mTimerSource);
}
#endif
timer->Release();
Signal();
}
CHIP_ERROR LayerImplSelect::ScheduleWork(TimerCompleteCallback onComplete, void * appState)
{
VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
CancelTimer(onComplete, appState);
Timer * timer = Timer::New(*this, 0, onComplete, appState);
VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
dispatch_queue_t dispatchQueue = GetDispatchQueue();
if (dispatchQueue)
{
(void) mTimerList.Add(timer);
dispatch_async(dispatchQueue, ^{
this->HandleTimerComplete(timer);
});
return CHIP_NO_ERROR;
}
#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
if (mTimerList.Add(timer) == timer)
{
// The new timer is the earliest, so the time until the next event has probably changed.
Signal();
}
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::StartWatchingSocket(int fd, SocketWatchToken * tokenOut)
{
// Find a free slot.
SocketWatch * watch = nullptr;
for (auto & w : mSocketWatchPool)
{
if (w.mFD == fd)
{
// Duplicate registration is an error.
return CHIP_ERROR_INVALID_ARGUMENT;
}
else if ((w.mFD == kInvalidFd) && (watch == nullptr))
{
watch = &w;
}
}
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_ENDPOINT_POOL_FULL);
watch->mFD = fd;
*tokenOut = reinterpret_cast<SocketWatchToken>(watch);
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::SetCallback(SocketWatchToken token, SocketWatchCallback callback, intptr_t data)
{
SocketWatch * watch = reinterpret_cast<SocketWatch *>(token);
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
watch->mCallback = callback;
watch->mCallbackData = data;
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::RequestCallbackOnPendingRead(SocketWatchToken token)
{
SocketWatch * watch = reinterpret_cast<SocketWatch *>(token);
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
watch->mPendingIO.Set(SocketEventFlags::kRead);
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::RequestCallbackOnPendingWrite(SocketWatchToken token)
{
SocketWatch * watch = reinterpret_cast<SocketWatch *>(token);
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
watch->mPendingIO.Set(SocketEventFlags::kWrite);
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::ClearCallbackOnPendingRead(SocketWatchToken token)
{
SocketWatch * watch = reinterpret_cast<SocketWatch *>(token);
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
watch->mPendingIO.Clear(SocketEventFlags::kRead);
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::ClearCallbackOnPendingWrite(SocketWatchToken token)
{
SocketWatch * watch = reinterpret_cast<SocketWatch *>(token);
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
watch->mPendingIO.Clear(SocketEventFlags::kWrite);
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplSelect::StopWatchingSocket(SocketWatchToken * tokenInOut)
{
SocketWatch * watch = reinterpret_cast<SocketWatch *>(*tokenInOut);
*tokenInOut = InvalidSocketWatchToken();
VerifyOrReturnError(watch != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(watch->mFD >= 0, CHIP_ERROR_INCORRECT_STATE);
watch->Clear();
// Wake the thread calling select so that it stops selecting on the socket.
Signal();
return CHIP_NO_ERROR;
}
/**
* Set the read, write or exception bit flags for the specified socket based on its status in
* the corresponding file descriptor sets.
*
* @param[in] socket The file descriptor for which the bit flags are being set.
*
* @param[in] readfds A pointer to the set of readable file descriptors.
*
* @param[in] writefds A pointer to the set of writable file descriptors.
*
* @param[in] exceptfds A pointer to the set of file descriptors with errors.
*/
SocketEvents LayerImplSelect::SocketEventsFromFDs(int socket, const fd_set & readfds, const fd_set & writefds,
const fd_set & exceptfds)
{
SocketEvents res;
if (socket >= 0)
{
// POSIX does not define the fd_set parameter of FD_ISSET() as const, even though it isn't modified.
if (FD_ISSET(socket, const_cast<fd_set *>(&readfds)))
res.Set(SocketEventFlags::kRead);
if (FD_ISSET(socket, const_cast<fd_set *>(&writefds)))
res.Set(SocketEventFlags::kWrite);
if (FD_ISSET(socket, const_cast<fd_set *>(&exceptfds)))
res.Set(SocketEventFlags::kExcept);
}
return res;
}
void LayerImplSelect::PrepareEvents()
{
assertChipStackLockedByCurrentThread();
constexpr Clock::MonotonicMilliseconds kMaxTimeout =
static_cast<Clock::MonotonicMilliseconds>(DEFAULT_MIN_SLEEP_PERIOD) * kMillisecondsPerSecond;
const Clock::MonotonicMilliseconds currentTime = Clock::GetMonotonicMilliseconds();
Clock::MonotonicMilliseconds awakenTime = currentTime + kMaxTimeout;
Timer * timer = mTimerList.Earliest();
if (timer && Clock::IsEarlier(timer->AwakenTime(), awakenTime))
{
awakenTime = timer->AwakenTime();
}
const Clock::MonotonicMilliseconds sleepTime = (awakenTime > currentTime) ? (awakenTime - currentTime) : 0;
Clock::MillisecondsToTimeval(sleepTime, mNextTimeout);
#if CHIP_DEVICE_CONFIG_ENABLE_MDNS && !__ZEPHYR__ && !__MBED__
chip::Mdns::GetMdnsTimeout(mNextTimeout);
#endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS && !__ZEPHYR__
mMaxFd = -1;
FD_ZERO(&mSelected.mReadSet);
FD_ZERO(&mSelected.mWriteSet);
FD_ZERO(&mSelected.mErrorSet);
for (auto & w : mSocketWatchPool)
{
if (w.mFD != kInvalidFd)
{
if (mMaxFd < w.mFD)
{
mMaxFd = w.mFD;
}
if (w.mPendingIO.Has(SocketEventFlags::kRead))
{
FD_SET(w.mFD, &mSelected.mReadSet);
}
if (w.mPendingIO.Has(SocketEventFlags::kWrite))
{
FD_SET(w.mFD, &mSelected.mWriteSet);
}
}
}
}
void LayerImplSelect::WaitForEvents()
{
mSelectResult = select(mMaxFd + 1, &mSelected.mReadSet, &mSelected.mWriteSet, &mSelected.mErrorSet, &mNextTimeout);
}
void LayerImplSelect::HandleEvents()
{
assertChipStackLockedByCurrentThread();
if (mSelectResult < 0)
{
ChipLogError(DeviceLayer, "select failed: %s\n", ErrorStr(System::MapErrorPOSIX(errno)));
return;
}
#if CHIP_SYSTEM_CONFIG_POSIX_LOCKING
mHandleSelectThread = pthread_self();
#endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
// Obtain the list of currently expired timers. Any new timers added by timer callback are NOT handled on this pass,
// since that could result in infinite handling of new timers blocking any other progress.
Timer::List expiredTimers(mTimerList.ExtractEarlier(1 + Clock::GetMonotonicMilliseconds()));
Timer * timer = nullptr;
while ((timer = expiredTimers.PopEarliest()) != nullptr)
{
timer->HandleComplete();
}
for (auto & w : mSocketWatchPool)
{
if (w.mFD != kInvalidFd)
{
SocketEvents events = SocketEventsFromFDs(w.mFD, mSelected.mReadSet, mSelected.mWriteSet, mSelected.mErrorSet);
if (events.HasAny() && w.mCallback != nullptr)
{
w.mCallback(events, w.mCallbackData);
}
}
}
#if CHIP_DEVICE_CONFIG_ENABLE_MDNS && !__ZEPHYR__ && !__MBED__
chip::Mdns::HandleMdnsTimeout();
#endif // CHIP_DEVICE_CONFIG_ENABLE_MDNS && !__ZEPHYR__
#if CHIP_SYSTEM_CONFIG_POSIX_LOCKING
mHandleSelectThread = PTHREAD_NULL;
#endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING
}
#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
void LayerImplSelect::HandleTimerComplete(Timer * timer)
{
mTimerList.Remove(timer);
timer->HandleComplete();
}
#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH
void LayerImplSelect::SocketWatch::Clear()
{
mFD = kInvalidFd;
mPendingIO.ClearAll();
mCallback = nullptr;
mCallbackData = 0;
}
} // namespace System
} // namespace chip