| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2026 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "Condition.h" | ||
| 16 | #include "../Timers.h" | ||
| 17 | #include "../common.h" | ||
| 18 | #include "../fpp-json.h" | ||
| 19 | #include "../log.h" | ||
| 20 | |||
| 21 | #include "IfCommand.h" | ||
| 22 | |||
| 23 | #include <mutex> | ||
| 24 | |||
| 25 | 1888 | IfCommand::IfCommand() : | |
| 26 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | LocalOnlyCommand("If") { |
| 27 | // One condition-tree editor, not two competing ways to say the same | ||
| 28 | // thing - a single Check/Is/Value field pair alongside a separate, | ||
| 29 | // Advanced-only "Rules" box that could add more conditions was | ||
| 30 | // confusing (which one is actually being used?). The "conditionlist" | ||
| 31 | // widget already supports exactly "one condition" through "several | ||
| 32 | // conditions with AND/OR" in one place (fpp.js | ||
| 33 | // RenderConditionListRows only shows the AND/OR toggle once there are | ||
| 34 | // 2+ rows, so the single-condition case still looks simple) - it's | ||
| 35 | // always visible now, not gated behind UI Level. | ||
| 36 |
4/8✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
|
13216 | args.push_back(CommandArg("conditions", "conditionlist", "Check") |
| 37 |
3/6✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
|
3776 | .setHelp("What to test. With more than one condition, choose whether ALL or ANY of them must be true.")); |
| 38 |
4/8✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
|
13216 | args.push_back(CommandArg("thenCommands", "commandlist", "Then Run") |
| 39 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .setSection("When Check is True") |
| 40 |
3/6✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
|
3776 | .setHelp("Commands run when the check above is True.")); |
| 41 |
4/8✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
|
13216 | args.push_back(CommandArg("thenMode", "string", "Then Run") |
| 42 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .setSection("When Check is True") |
| 43 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
5664 | .setContentList({ "Sequential", "Parallel" }) |
| 44 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | .setDefaultValue("Sequential") |
| 45 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | .setToggleStyle() |
| 46 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .setToggleLabel("Order") |
| 47 |
3/6✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
|
3776 | .setHelp("Sequential waits for each command to finish before starting the next. " |
| 48 | "Parallel fires them all together without waiting.")); | ||
| 49 |
4/8✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
|
13216 | args.push_back(CommandArg("elseCommands", "commandlist", "Otherwise Run") |
| 50 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | .setDefaultValue("") |
| 51 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .setSection("When Check is False") |
| 52 |
3/6✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
|
3776 | .setHelp("Commands run when the check above is False. Leave empty to do nothing.")); |
| 53 |
4/8✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1888 times.
✗ Branch 7 not taken.
|
13216 | args.push_back(CommandArg("elseMode", "string", "Otherwise Run") |
| 54 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .setSection("When Check is False") |
| 55 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
5664 | .setContentList({ "Sequential", "Parallel" }) |
| 56 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | .setDefaultValue("Sequential") |
| 57 |
1/2✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
|
1888 | .setToggleStyle() |
| 58 |
2/4✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
|
3776 | .setToggleLabel("Order") |
| 59 |
3/6✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1888 times.
✗ Branch 5 not taken.
|
3776 | .setHelp("Sequential waits for each command to finish before starting the next. " |
| 60 | "Parallel fires them all together without waiting.")); | ||
| 61 | 1888 | } | |
| 62 | |||
| 63 | namespace | ||
| 64 | { | ||
| 65 | |||
| 66 | struct CommandListEntry { | ||
| 67 | std::string command; | ||
| 68 | std::vector<std::string> args; | ||
| 69 | }; | ||
| 70 | |||
| 71 | 1007 | std::vector<CommandListEntry> ParseCommandList(const std::string& json) { | |
| 72 | 1007 | std::vector<CommandListEntry> result; | |
| 73 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1007 times.
|
1007 | if (json.empty()) { |
| 74 | ✗ | return result; | |
| 75 | } | ||
| 76 |
1/2✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
|
1007 | Json::Value root; |
| 77 |
2/4✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1007 times.
|
1007 | if (!LoadJsonFromString(json, root)) { |
| 78 | ✗ | return result; // LoadJsonFromString() already logged the parse error | |
| 79 | } | ||
| 80 |
2/4✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1007 times.
|
1007 | if (!root.isArray()) { |
| 81 | ✗ | LogWarn(VB_COMMAND, "If: command list JSON was valid but not an array, treating as empty: %s\n", | |
| 82 | TruncateForLog(json).c_str()); | ||
| 83 | ✗ | return result; | |
| 84 | } | ||
| 85 |
7/12✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1015 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1015 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2022 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1015 times.
✓ Branch 11 taken 1007 times.
|
2022 | for (auto& entry : root) { |
| 86 |
3/4✓ Branch 0 taken 1015 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1012 times.
|
1015 | if (!entry.isObject()) { |
| 87 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
|
3 | LogWarn(VB_COMMAND, "If: command list entry was not a JSON object, skipping: %s\n", |
| 88 | TruncateForLog(SaveJsonToString(entry)).c_str()); | ||
| 89 | 3 | continue; | |
| 90 | } | ||
| 91 | 1012 | CommandListEntry e; | |
| 92 |
3/6✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
|
1012 | e.command = entry.get("command", "").asString(); |
| 93 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1012 times.
|
1012 | if (e.command.empty()) { |
| 94 | ✗ | continue; | |
| 95 | } | ||
| 96 |
6/12✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1012 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1012 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1012 times.
✗ Branch 11 not taken.
|
1012 | if (entry.isMember("args") && entry["args"].isArray()) { |
| 97 |
7/12✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7060 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8072 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 7060 times.
✓ Branch 11 taken 1012 times.
|
8072 | for (auto& a : entry["args"]) { |
| 98 |
3/6✓ Branch 0 taken 7060 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7060 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7060 times.
✗ Branch 5 not taken.
|
7060 | e.args.push_back(a.asString()); |
| 99 | } | ||
| 100 | } | ||
| 101 |
1/2✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
|
1012 | result.push_back(e); |
| 102 |
1/2✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
|
1012 | } |
| 103 | 1007 | return result; | |
| 104 | 1007 | } | |
| 105 | |||
| 106 | 1012 | std::string DescribeEntry(const CommandListEntry& entry) { | |
| 107 | 1012 | std::string argsStr; | |
| 108 |
2/2✓ Branch 0 taken 7060 times.
✓ Branch 1 taken 1012 times.
|
8072 | for (auto& a : entry.args) { |
| 109 |
2/2✓ Branch 0 taken 6048 times.
✓ Branch 1 taken 1012 times.
|
7060 | if (!argsStr.empty()) { |
| 110 |
1/2✓ Branch 0 taken 6048 times.
✗ Branch 1 not taken.
|
6048 | argsStr += ", "; |
| 111 | } | ||
| 112 |
4/8✓ Branch 0 taken 7060 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7060 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7060 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7060 times.
✗ Branch 7 not taken.
|
7060 | argsStr += "\"" + TruncateForLog(a) + "\""; |
| 113 | } | ||
| 114 |
3/6✓ Branch 0 taken 1012 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1012 times.
✗ Branch 5 not taken.
|
2024 | return entry.command + "(" + argsStr + ")"; |
| 115 | 1012 | } | |
| 116 | |||
| 117 | // A Sequential Then/Else list that isn't finished yet - the next entry | ||
| 118 | // in "remaining" starts as soon as "inFlight" (the currently-running | ||
| 119 | // entry's Result) reports isDone(). Never blocks the thread that called | ||
| 120 | // If::run(): AdvancePendingChains() (below) is what actually advances | ||
| 121 | // this, driven by a self-re-arming one-shot Timers timer that exists | ||
| 122 | // only while chains are pending (same pattern as | ||
| 123 | // RecurringTasks::armPollTimer()). This replaces the old sleep-loop - | ||
| 124 | // busy-waiting was fine from an HTTP handler thread, but If is also | ||
| 125 | // reached synchronously from Sequence::SendSequenceData() (the | ||
| 126 | // per-frame playback path, via a frame-triggered Command Preset) and | ||
| 127 | // from GPIO edge handlers, where a 1-second stall is a real problem. | ||
| 128 | struct PendingChain { | ||
| 129 | std::vector<CommandListEntry> remaining; | ||
| 130 | std::unique_ptr<Command::Result> inFlight; | ||
| 131 | long long startTimeMS = 0; | ||
| 132 | }; | ||
| 133 | |||
| 134 | // Same rationale as RecurringTasks::PENDING_RESULT_MAX_MS - don't let a | ||
| 135 | // single hung command (e.g. a URL fetch with no server-side timeout) | ||
| 136 | // keep a chain alive forever; drop it and move on, same as a normal | ||
| 137 | // Sequential wait giving up after its own bounded poll used to. | ||
| 138 | constexpr long long PENDING_CHAIN_MAX_MS = 60000; | ||
| 139 | |||
| 140 | // How often to re-check an in-flight entry's Result while chains are | ||
| 141 | // pending - roughly the old worst-case advance latency, when the fppd | ||
| 142 | // main loop called a tick every iteration at up to 50ms sleeps. | ||
| 143 | constexpr long long PENDING_POLL_MS = 50; | ||
| 144 | |||
| 145 | std::mutex g_pendingLock; | ||
| 146 | std::vector<PendingChain> g_pendingChains; | ||
| 147 | |||
| 148 | void AdvancePendingChains(); | ||
| 149 | |||
| 150 | // One-shot rather than periodic for the same reason as | ||
| 151 | // RecurringTasks::armPollTimer(): a periodic timer stopping itself from | ||
| 152 | // inside its own callback would delete the std::function currently | ||
| 153 | // executing. Every push into g_pendingChains is followed by an arm on | ||
| 154 | // the same thread (RunCommandList runs on arbitrary threads - HTTP | ||
| 155 | // handlers, GPIO edges, the frame path - and Timers::addTimer is | ||
| 156 | // thread-safe), so an advance can never be missed; at worst an arm | ||
| 157 | // races an in-progress advance and the extra fire finds nothing to do. | ||
| 158 | 122 | void ArmChainPollTimer() { | |
| 159 |
3/6✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 122 times.
✗ Branch 5 not taken.
|
366 | Timers::INSTANCE.addTimer("IfCommand-pendingChains", GetTimeMS() + PENDING_POLL_MS, []() { |
| 160 | 122 | AdvancePendingChains(); | |
| 161 | 122 | }); | |
| 162 | 122 | } | |
| 163 | |||
| 164 | // Starts chain.remaining.front() (removing it from the queue) as the new | ||
| 165 | // inFlight entry, or clears inFlight if the chain is exhausted - the | ||
| 166 | // caller (either RunCommandList kicking a chain off, or tick() advancing | ||
| 167 | // one) is responsible for then deciding whether to keep the chain around. | ||
| 168 | 1009 | void StartNextEntry(PendingChain& chain) { | |
| 169 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1009 times.
|
1009 | if (chain.remaining.empty()) { |
| 170 | ✗ | chain.inFlight.reset(); | |
| 171 | ✗ | return; | |
| 172 | } | ||
| 173 | 1009 | CommandListEntry entry = std::move(chain.remaining.front()); | |
| 174 |
1/2✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
|
1009 | chain.remaining.erase(chain.remaining.begin()); |
| 175 | // Deliberately not gated on WillLog(): a line at Debug or below is | ||
| 176 | // retained in the crash ring even when the configured level discards it | ||
| 177 | // (see CrashLogRingWillCapture), so gating would drop the If tracing | ||
| 178 | // out of crash dumps - which is exactly where it earns its keep. | ||
| 179 |
2/4✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1009 times.
✗ Branch 3 not taken.
|
1009 | LogDebug(VB_COMMAND, "If: running command %s\n", DescribeEntry(entry).c_str()); |
| 180 |
1/2✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
|
1009 | chain.inFlight = CommandManager::INSTANCE.run(entry.command, entry.args); |
| 181 |
1/2✓ Branch 0 taken 1009 times.
✗ Branch 1 not taken.
|
1009 | chain.startTimeMS = GetMonotonicTimeMS(); |
| 182 | 1009 | } | |
| 183 | |||
| 184 | // Sequential: fires the first entry, then queues the rest as a | ||
| 185 | // PendingChain that IfCommand::tick() advances asynchronously - never | ||
| 186 | // waits on this thread. Parallel: the exact loop shape TriggerPreset | ||
| 187 | // already uses (fire each entry, never wait on isDone() between them) - | ||
| 188 | // unchanged, was never the source of the stall risk. | ||
| 189 | 1007 | void RunCommandList(std::vector<CommandListEntry> list, bool sequential) { | |
| 190 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1007 times.
|
1007 | if (list.empty()) { |
| 191 | 1005 | return; | |
| 192 | } | ||
| 193 |
3/4✓ Branch 0 taken 1006 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
|
1007 | LogDebug(VB_COMMAND, "If: running %zu command(s) %s\n", list.size(), sequential ? "sequentially" : "in parallel"); |
| 194 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1006 times.
|
1007 | if (!sequential) { |
| 195 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | for (auto& entry : list) { |
| 196 | // Ungated for the same reason as StartNextEntry() above. | ||
| 197 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | LogDebug(VB_COMMAND, "If: running command %s\n", DescribeEntry(entry).c_str()); |
| 198 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | CommandManager::INSTANCE.run(entry.command, entry.args); |
| 199 | } | ||
| 200 | 1 | return; | |
| 201 | } | ||
| 202 | 1006 | PendingChain chain; | |
| 203 | 1006 | chain.remaining = std::move(list); | |
| 204 |
1/2✓ Branch 0 taken 1006 times.
✗ Branch 1 not taken.
|
1006 | StartNextEntry(chain); |
| 205 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1006 times.
|
1006 | if (!chain.inFlight) { |
| 206 | ✗ | return; // shouldn't happen (list wasn't empty), but nothing to track either way | |
| 207 | } | ||
| 208 |
3/4✓ Branch 0 taken 1006 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1004 times.
✓ Branch 3 taken 2 times.
|
1006 | if (chain.inFlight->isDone()) { |
| 209 | // Finished synchronously (most commands do) - just keep going on | ||
| 210 | // this thread rather than parking a chain for tick() to find | ||
| 211 | // already-done on its very next pass. | ||
| 212 |
4/8✓ Branch 0 taken 1004 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1004 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1004 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1004 times.
|
1004 | while (chain.inFlight->isDone() && !chain.remaining.empty()) { |
| 213 | ✗ | StartNextEntry(chain); | |
| 214 | } | ||
| 215 |
4/8✓ Branch 0 taken 1004 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1004 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1004 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1004 times.
✗ Branch 7 not taken.
|
1004 | if (chain.inFlight && chain.inFlight->isDone()) { |
| 216 | 1004 | return; // fully drained synchronously | |
| 217 | } | ||
| 218 | } | ||
| 219 | { | ||
| 220 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | std::unique_lock<std::mutex> l(g_pendingLock); |
| 221 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | g_pendingChains.push_back(std::move(chain)); |
| 222 | 2 | } | |
| 223 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ArmChainPollTimer(); |
| 224 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1004 times.
|
1006 | } |
| 225 | |||
| 226 | 122 | void AdvancePendingChains() { | |
| 227 | 122 | std::vector<PendingChain> toAdvance; | |
| 228 | { | ||
| 229 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | std::unique_lock<std::mutex> l(g_pendingLock); |
| 230 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
|
122 | if (g_pendingChains.empty()) { |
| 231 | ✗ | return; | |
| 232 | } | ||
| 233 | 122 | toAdvance = std::move(g_pendingChains); | |
| 234 | 122 | g_pendingChains.clear(); | |
| 235 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | } |
| 236 | 122 | std::vector<PendingChain> stillPending; | |
| 237 |
2/2✓ Branch 0 taken 122 times.
✓ Branch 1 taken 122 times.
|
244 | for (auto& chain : toAdvance) { |
| 238 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | bool timedOut = (GetMonotonicTimeMS() - chain.startTimeMS) > PENDING_CHAIN_MAX_MS; |
| 239 |
2/10✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 122 times.
|
122 | if (timedOut && chain.inFlight && !chain.inFlight->isDone()) { |
| 240 | ✗ | LogWarn(VB_COMMAND, "If: a Sequential command did not finish within %lldms, abandoning the rest of its chain\n", | |
| 241 | PENDING_CHAIN_MAX_MS); | ||
| 242 | ✗ | continue; // drop the whole chain, same as RecurringTasks giving up on a hung task | |
| 243 | } | ||
| 244 |
8/10✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 120 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 122 times.
|
125 | while (chain.inFlight && chain.inFlight->isDone() && !chain.remaining.empty()) { |
| 245 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | StartNextEntry(chain); |
| 246 | } | ||
| 247 |
6/8✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 122 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 120 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 120 times.
✓ Branch 7 taken 2 times.
|
122 | if (chain.inFlight && !chain.inFlight->isDone()) { |
| 248 |
1/2✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
|
120 | stillPending.push_back(std::move(chain)); |
| 249 | } | ||
| 250 | // else: inFlight finished and remaining is empty - chain is done, drop it. | ||
| 251 | } | ||
| 252 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 2 times.
|
122 | if (!stillPending.empty()) { |
| 253 | { | ||
| 254 |
1/2✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
|
120 | std::unique_lock<std::mutex> l(g_pendingLock); |
| 255 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 120 times.
|
240 | for (auto& chain : stillPending) { |
| 256 |
1/2✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
|
120 | g_pendingChains.push_back(std::move(chain)); |
| 257 | } | ||
| 258 | 120 | } | |
| 259 |
1/2✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
|
120 | ArmChainPollTimer(); |
| 260 | } | ||
| 261 |
1/2✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
|
122 | } |
| 262 | |||
| 263 | } // namespace | ||
| 264 | |||
| 265 | 1008 | std::unique_ptr<Command::Result> IfCommand::run(const std::vector<std::string>& args) { | |
| 266 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1008 times.
|
1008 | if (args.size() < 5) { |
| 267 | ✗ | return std::make_unique<Command::ErrorResult>( | |
| 268 | ✗ | "If requires conditions, thenCommands, thenMode, elseCommands, elseMode"); | |
| 269 | } | ||
| 270 | 1008 | const std::string& conditions = args[0]; | |
| 271 | 1008 | const std::string& thenCommandsJson = args[1]; | |
| 272 |
1/2✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
|
1008 | bool thenSequential = args[2] != "Parallel"; |
| 273 | 1008 | const std::string& elseCommandsJson = args[3]; | |
| 274 |
1/2✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
|
1008 | bool elseSequential = args[4] != "Parallel"; |
| 275 | |||
| 276 |
2/4✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1008 times.
✗ Branch 3 not taken.
|
1008 | LogExcess(VB_COMMAND, "If: Check = %s\n", TruncateForLog(conditions, 2000).c_str()); |
| 277 | |||
| 278 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1008 times.
|
1008 | if (conditions.empty()) { |
| 279 | ✗ | return std::make_unique<Command::ErrorResult>("If: no condition configured under Check"); | |
| 280 | } | ||
| 281 |
1/2✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
|
1008 | Json::Value conditionsJson; |
| 282 | 1008 | std::unique_ptr<ConditionNode> condition; | |
| 283 |
2/4✓ Branch 0 taken 1008 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1008 times.
✗ Branch 3 not taken.
|
1008 | if (LoadJsonFromString(conditions, conditionsJson)) { |
| 284 |
2/2✓ Branch 0 taken 1007 times.
✓ Branch 1 taken 1 times.
|
1008 | condition = ConditionNode::FromJSON(conditionsJson); |
| 285 | } | ||
| 286 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1007 times.
|
1007 | if (!condition) { |
| 287 | ✗ | return std::make_unique<Command::ErrorResult>("If: could not parse the condition(s) under Check"); | |
| 288 | } | ||
| 289 | |||
| 290 |
1/2✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
|
1007 | bool matched = condition->evaluate(); |
| 291 | // Two tiers, matching the rest of the Command facility: Debug carries the | ||
| 292 | // decision - the causal link between this If and the commands that follow, | ||
| 293 | // which each log themselves at Info from CommandManager::run() - while the | ||
| 294 | // command-list JSON goes to Excessive. That JSON is up to 2000 chars | ||
| 295 | // restating, less legibly, what those next lines already say, and Excessive | ||
| 296 | // is the one level the crash ring does not capture, so it stays out of the | ||
| 297 | // 256 slots the ring has to work with. | ||
| 298 |
5/6✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 977 times.
✓ Branch 3 taken 30 times.
✓ Branch 4 taken 1007 times.
✗ Branch 5 not taken.
|
1007 | LogDebug(VB_COMMAND, "If: overall result = %s -> running %s\n", |
| 299 | matched ? "true" : "false", matched ? "Then Run" : "Otherwise Run"); | ||
| 300 |
6/8✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 977 times.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 1007 times.
✗ Branch 7 not taken.
|
1007 | LogExcess(VB_COMMAND, "If: %s command list = %s\n", matched ? "Then Run" : "Otherwise Run", |
| 301 | TruncateForLog(matched ? thenCommandsJson : elseCommandsJson, 2000).c_str()); | ||
| 302 |
3/4✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
|
1007 | auto list = ParseCommandList(matched ? thenCommandsJson : elseCommandsJson); |
| 303 |
2/2✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
|
1007 | bool sequential = matched ? thenSequential : elseSequential; |
| 304 | |||
| 305 |
1/2✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
|
1007 | if (!list.empty()) { |
| 306 |
1/2✓ Branch 0 taken 1007 times.
✗ Branch 1 not taken.
|
1007 | RunCommandList(std::move(list), sequential); |
| 307 | } else { | ||
| 308 | ✗ | LogDebug(VB_COMMAND, "If: %s has no commands configured, nothing to run\n", | |
| 309 | matched ? "Then Run" : "Otherwise Run"); | ||
| 310 | } | ||
| 311 | |||
| 312 |
3/4✓ Branch 0 taken 977 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 1007 times.
✗ Branch 3 not taken.
|
1007 | return std::make_unique<Command::Result>(matched ? "true" : "false"); |
| 313 | 1009 | } | |
| 314 |