mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-12 21:27:39 -04:00
109 lines
3.5 KiB
C++
109 lines
3.5 KiB
C++
/*
|
|
* This file is part of the ZoneMinder Project. See AUTHORS file for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "zm_catch2.h"
|
|
|
|
#include "zm_stream.h"
|
|
|
|
#include <atomic>
|
|
#include <thread>
|
|
|
|
// zms runs two threads over one StreamBase: the command thread
|
|
// (checkCommandQueue -> processCommand) writes the playback state, and the
|
|
// streaming loop (runStream) reads it every iteration. Nothing synchronises
|
|
// them, so before zoneminder/zoneminder#4939 every one of these fields was a
|
|
// data race, which is undefined behaviour regardless of what it does on x86.
|
|
//
|
|
// This exercises those exact members from two threads. It passes trivially in a
|
|
// normal build; its value is under ThreadSanitizer:
|
|
//
|
|
// cmake -DTSAN=ON -DBUILD_TEST_SUITE=ON .. && cmake --build .
|
|
// ./tests/tests "[races]"
|
|
//
|
|
// With the fields plain, TSan reports a write/read race on each. With them
|
|
// atomic it is clean.
|
|
|
|
namespace {
|
|
|
|
class PlaybackStateProbe : public StreamBase {
|
|
public:
|
|
// The writes MonitorStream::processCommand performs on the command thread.
|
|
void applyCommands(int iterations) {
|
|
for (int i = 0; i < iterations; i++) {
|
|
stopped = false;
|
|
paused = true;
|
|
replay_rate = ZM_RATE_BASE * ((i % 5) + 1);
|
|
scale = 100 + (i % 100);
|
|
zoom = 100 + (i % 50);
|
|
x = static_cast<unsigned short>(i % 1000);
|
|
y = static_cast<unsigned short>(i % 800);
|
|
step = (i % 3) - 1;
|
|
send_twice = (i % 2) == 0;
|
|
maxfps = 1.0 + (i % 30);
|
|
frame_type = (i % 2) ? FRAME_ANALYSIS : FRAME_NORMAL;
|
|
paused = false;
|
|
}
|
|
}
|
|
|
|
// The reads runStream() performs on the streaming thread. The accumulator
|
|
// exists so none of this is optimised away.
|
|
int64 readPlaybackState(int iterations) {
|
|
int64 sink = 0;
|
|
for (int i = 0; i < iterations; i++) {
|
|
sink += paused ? 1 : 0;
|
|
sink += stopped ? 1 : 0;
|
|
sink += replay_rate;
|
|
sink += scale;
|
|
sink += zoom;
|
|
sink += x;
|
|
sink += y;
|
|
sink += step;
|
|
sink += send_twice ? 1 : 0;
|
|
sink += static_cast<int64>(maxfps);
|
|
sink += (frame_type == FRAME_ANALYSIS) ? 1 : 0;
|
|
}
|
|
return sink;
|
|
}
|
|
|
|
protected:
|
|
void processCommand(const CmdMsg *) override {}
|
|
void runStream() override {}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("StreamBase playback state is shared safely between the command and stream threads", "[races]") {
|
|
// connkey defaults to 0, so ~StreamBase does no socket teardown here.
|
|
PlaybackStateProbe probe;
|
|
|
|
constexpr int kIterations = 20000;
|
|
std::atomic<int64> observed{0};
|
|
|
|
std::thread command_thread([&probe]() { probe.applyCommands(kIterations); });
|
|
std::thread stream_thread([&probe, &observed]() {
|
|
observed = probe.readPlaybackState(kIterations);
|
|
});
|
|
|
|
command_thread.join();
|
|
stream_thread.join();
|
|
|
|
// The exact total is timing dependent and deliberately not asserted. What
|
|
// matters is that both threads completed and that, under TSan, the run
|
|
// produced no race report.
|
|
REQUIRE(observed.load() >= 0);
|
|
}
|