Implemented strict locking of global lists

This commit is contained in:
Klaus Schmidinger
2015-09-01 11:14:27 +02:00
parent 8a7bc6a0bb
commit 3cd5294d8a
41 changed files with 3512 additions and 2402 deletions

298
vdr.c
View File

@@ -22,7 +22,7 @@
*
* The project's page is at http://www.tvdr.de
*
* $Id: vdr.c 4.4 2015/05/21 13:58:33 kls Exp $
* $Id: vdr.c 4.5 2015/09/01 10:33:04 kls Exp $
*/
#include <getopt.h>
@@ -748,8 +748,8 @@ int main(int argc, char *argv[])
Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
cChannels::Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
cTimers::Load(AddDirectory(ConfigDirectory, "timers.conf"));
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
@@ -765,8 +765,7 @@ int main(int argc, char *argv[])
// Recordings:
Recordings.Update();
DeletedRecordings.Update();
cRecordings::Update();
// EPG data:
@@ -879,16 +878,20 @@ int main(int argc, char *argv[])
if (!CamSlots.WaitForAllCamSlotsReady(DEVICEREADYTIMEOUT))
dsyslog("not all CAM slots ready after %d seconds", DEVICEREADYTIMEOUT);
if (*Setup.InitialChannel) {
LOCK_CHANNELS_READ;
if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
if (const cChannel *Channel = Channels->GetByNumber(atoi(Setup.InitialChannel)))
Setup.InitialChannel = Channel->GetChannelID().ToString();
}
if (cChannel *Channel = Channels.GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
if (const cChannel *Channel = Channels->GetByChannelID(tChannelID::FromString(Setup.InitialChannel)))
Setup.CurrentChannel = Channel->Number();
}
if (Setup.InitialVolume >= 0)
Setup.CurrentVolume = Setup.InitialVolume;
Channels.SwitchTo(Setup.CurrentChannel);
{
LOCK_CHANNELS_READ;
Channels->SwitchTo(Setup.CurrentChannel);
}
if (MuteAudio)
cDevice::PrimaryDevice()->ToggleMute();
else
@@ -936,13 +939,14 @@ int main(int argc, char *argv[])
static time_t lastTime = 0;
if (!cDevice::PrimaryDevice()->HasProgramme()) {
if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel());
LOCK_CHANNELS_READ;
const cChannel *Channel = Channels->GetByNumber(cDevice::CurrentChannel());
if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(Channel->Number())) // try to switch to the original channel...
;
else if (LastTimerChannel > 0) {
Channel = Channels.GetByNumber(LastTimerChannel);
if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
Channel = Channels->GetByNumber(LastTimerChannel);
if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels->SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
;
}
}
@@ -970,40 +974,59 @@ int main(int argc, char *argv[])
}
}
// Handle channel and timer modifications:
if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
int modified = Channels.Modified();
static time_t ChannelSaveTimeout = 0;
static int TimerState = 0;
// Channels and timers need to be stored in a consistent manner,
// therefore if one of them is changed, we save both.
if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
ChannelSaveTimeout = 1; // triggers an immediate save
else if (modified && !ChannelSaveTimeout)
ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
if ((modified || timeout) && Channels.Lock(false, 100)) {
if (timeout) {
Channels.Save();
Timers.Save();
ChannelSaveTimeout = 0;
{
// Channels and timers need to be stored in a consistent manner,
// therefore if one of them is changed, we save both.
static time_t ChannelSaveTimeout = 0;
static cStateKey TimersStateKey(true);
static cStateKey ChannelsStateKey(true);
static int ChannelsModifiedByUser = 0;
const cTimers *Timers = cTimers::GetTimersRead(TimersStateKey);
const cChannels *Channels = cChannels::GetChannelsRead(ChannelsStateKey);
if (ChannelSaveTimeout != 1) {
if (Channels) {
if (Channels->ModifiedByUser(ChannelsModifiedByUser))
ChannelSaveTimeout = 1; // triggers an immediate save
else if (!ChannelSaveTimeout)
ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
}
if (Timers)
ChannelSaveTimeout = 1; // triggers an immediate save
}
if (ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active())
ChannelSaveTimeout = 1; // triggers an immediate save
if (Timers && Channels) {
Channels->Save();
Timers->Save();
ChannelSaveTimeout = 0;
}
if (Channels) {
for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
if (Channel->Modification(CHANNELMOD_RETUNE)) {
cRecordControls::ChannelDataModified(Channel);
if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
Channels->SwitchTo(Channel->Number());
}
}
}
cStatus::MsgChannelChange(Channel);
}
}
for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
if (Channel->Modification(CHANNELMOD_RETUNE)) {
cRecordControls::ChannelDataModified(Channel);
if (Channel->Number() == cDevice::CurrentChannel() && cDevice::PrimaryDevice()->HasDecoder()) {
if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
isyslog("retuning due to modification of channel %d (%s)", Channel->Number(), Channel->Name());
Channels.SwitchTo(Channel->Number());
}
}
}
cStatus::MsgChannelChange(Channel);
}
}
Channels.Unlock();
}
}
}
// State keys are removed in reverse order!
if (Channels)
ChannelsStateKey.Remove();
if (Timers)
TimersStateKey.Remove();
if (ChannelSaveTimeout == 1) {
// Only one of them was modified, so we reset the state keys to handle them both in the next turn:
ChannelsStateKey.Reset();
TimersStateKey.Reset();
}
}
// Channel display:
if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
if (!Menu)
@@ -1013,80 +1036,109 @@ int main(int argc, char *argv[])
}
if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
// Timers and Recordings:
if (!Timers.BeingEdited()) {
// Assign events to timers:
Timers.SetEvents();
// Must do all following calls with the exact same time!
// Process ongoing recordings:
cRecordControls::Process(Now);
// Start new recordings:
cTimer *Timer = Timers.GetMatch(Now);
if (Timer) {
if (!cRecordControls::Start(Timer))
Timer->SetPending(true);
else
LastTimerChannel = Timer->Channel()->Number();
}
// Make sure timers "see" their channel early enough:
static time_t LastTimerCheck = 0;
if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
InhibitEpgScan = false;
for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
bool InVpsMargin = false;
bool NeedsTransponder = false;
if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
if (Timer->HasFlags(tfVps)) {
if (Timer->Matches(Now, true, Setup.VpsMargin)) {
InVpsMargin = true;
Timer->SetInVpsMargin(InVpsMargin);
}
else if (Timer->Event()) {
InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
}
else {
cSchedulesLock SchedulesLock;
const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
if (Schedules) {
const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
InVpsMargin = !Schedule; // we must make sure we have the schedule
NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
}
}
InhibitEpgScan |= InVpsMargin | NeedsTransponder;
}
else
NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
}
if (NeedsTransponder || InVpsMargin) {
// Find a device that provides the required transponder:
cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
if (!Device && InVpsMargin)
Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
// Switch the device to the transponder:
if (Device) {
bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
if (!Device->IsTunedToTransponder(Timer->Channel())) {
if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
if (Device->SwitchChannel(Timer->Channel(), false))
Device->SetOccupied(TIMERDEVICETIMEOUT);
}
if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
}
}
}
LastTimerCheck = Now;
}
// Delete expired timers:
Timers.DeleteExpired();
}
if (!Menu && Recordings.NeedsUpdate()) {
Recordings.Update();
DeletedRecordings.Update();
{
// Timers and Recordings:
bool TimersModified = false;
bool TriggerRemoteTimerPoll = false;
static cStateKey TimersStateKey(true);
if (cTimers::GetTimersRead(TimersStateKey)) {
TriggerRemoteTimerPoll = true;
TimersStateKey.Remove();
}
cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
// Get remote timers:
TimersModified |= Timers->GetRemoteTimers();
// Assign events to timers:
static cStateKey SchedulesStateKey;
if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey))
TimersModified |= Timers->SetEvents(Schedules);
// Must do all following calls with the exact same time!
// Process ongoing recordings:
if (cRecordControls::Process(Timers, Now)) {
TimersModified = true;
TriggerRemoteTimerPoll = true;
}
// Must keep the lock on the schedules until after processing the record
// controls, in order to avoid short interrupts in case the current event
// is replaced by a new one (which some broadcasters do, instead of just
// modifying the current event's data):
if (SchedulesStateKey.InLock())
SchedulesStateKey.Remove();
// Start new recordings:
if (cTimer *Timer = Timers->GetMatch(Now)) {
if (!cRecordControls::Start(Timers, Timer))
Timer->SetPending(true);
else
LastTimerChannel = Timer->Channel()->Number();
TimersModified = true;
TriggerRemoteTimerPoll = true;
}
// Make sure timers "see" their channel early enough:
static time_t LastTimerCheck = 0;
if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
InhibitEpgScan = false;
for (cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
if (Timer->Remote())
continue;
bool InVpsMargin = false;
bool NeedsTransponder = false;
if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
if (Timer->HasFlags(tfVps)) {
if (Timer->Matches(Now, true, Setup.VpsMargin)) {
InVpsMargin = true;
Timer->SetInVpsMargin(InVpsMargin);
}
else if (Timer->Event()) {
InVpsMargin = Timer->Event()->StartTime() <= Now && Now < Timer->Event()->EndTime();
NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
}
else {
LOCK_SCHEDULES_READ;
const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
InVpsMargin = !Schedule; // we must make sure we have the schedule
NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
}
InhibitEpgScan |= InVpsMargin | NeedsTransponder;
}
else
NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
}
if (NeedsTransponder || InVpsMargin) {
// Find a device that provides the required transponder:
cDevice *Device = cDevice::GetDeviceForTransponder(Timer->Channel(), MINPRIORITY);
if (!Device && InVpsMargin)
Device = cDevice::GetDeviceForTransponder(Timer->Channel(), LIVEPRIORITY);
// Switch the device to the transponder:
if (Device) {
bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
if (!Device->IsTunedToTransponder(Timer->Channel())) {
if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
dsyslog("switching device %d to channel %d (%s)", Device->DeviceNumber() + 1, Timer->Channel()->Number(), Timer->Channel()->Name());
if (Device->SwitchChannel(Timer->Channel(), false))
Device->SetOccupied(TIMERDEVICETIMEOUT);
}
if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
Skins.QueueMessage(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
}
}
}
LastTimerCheck = Now;
}
// Delete expired timers:
if (Timers->DeleteExpired()) {
TimersModified = true;
TriggerRemoteTimerPoll = true;
}
// Trigger remote timer polls:
if (TriggerRemoteTimerPoll)
Timers->TriggerRemoteTimerPoll();
TimersStateKey.Remove(TimersModified);
}
// Recordings:
if (!Menu) {
if (cRecordings::NeedsUpdate())
cRecordings::Update();
}
// CAM control:
if (!Menu && !cOsd::IsOpen())
@@ -1359,7 +1411,8 @@ int main(int argc, char *argv[])
case k0: {
if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
PreviousChannelIndex ^= 1;
Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
LOCK_CHANNELS_READ;
Channels->SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
break;
}
// Direct Channel Select:
@@ -1447,7 +1500,7 @@ int main(int argc, char *argv[])
// Disk housekeeping:
RemoveDeletedRecordings();
ClearVanishedRecordings();
ListGarbageCollector.Purge();
cSchedules::Cleanup();
// Plugins housekeeping:
PluginManager.Housekeeping();
@@ -1492,8 +1545,9 @@ Exit:
cPositioner::DestroyPositioner();
cVideoDirectory::Destroy();
EpgHandlers.Clear();
PluginManager.Shutdown(true);
cSchedules::Cleanup(true);
ListGarbageCollector.Purge(true);
PluginManager.Shutdown(true);
ReportEpgBugFixStats(true);
if (WatchdogTimeout > 0)
dsyslog("max. latency time %d seconds", MaxLatencyTime);