1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-01 15:43:44 +02:00

Complete the exception handling project, enable the throws

This commit is contained in:
Paul Licameli 2017-04-03 23:33:19 -04:00
commit d417acd5c6
12 changed files with 99 additions and 119 deletions

View File

@ -616,8 +616,7 @@ size_t BlockFile::CommonReadData(
if ( framesRead < len ) {
if (mayThrow)
//throw FileException{ FileException::Cause::Read, fileName }
;
throw FileException{ FileException::Cause::Read, fileName };
ClearSamples(data, format, framesRead, len - framesRead);
}

View File

@ -90,6 +90,7 @@
#include "AudacityApp.h"
#include "AudacityException.h"
#include "BlockFile.h"
#include "FileException.h"
#include "blockfile/LegacyBlockFile.h"
#include "blockfile/LegacyAliasBlockFile.h"
#include "blockfile/SimpleBlockFile.h"
@ -97,6 +98,7 @@
#include "blockfile/PCMAliasBlockFile.h"
#include "blockfile/ODPCMAliasBlockFile.h"
#include "blockfile/ODDecodeBlockFile.h"
#include "InconsistencyException.h"
#include "Internat.h"
#include "Project.h"
#include "Prefs.h"
@ -1110,8 +1112,12 @@ bool DirManager::ContainsBlockFile(const wxString &filepath) const
// Adds one to the reference count of the block file,
// UNLESS it is "locked", then it makes a NEW copy of
// the BlockFile.
// This function returns non-NULL, or else throws
BlockFilePtr DirManager::CopyBlockFile(const BlockFilePtr &b)
{
if (!b)
THROW_INCONSISTENCY_EXCEPTION;
auto result = b->GetFileName();
const auto &fn = result.name;
@ -1122,7 +1128,7 @@ BlockFilePtr DirManager::CopyBlockFile(const BlockFilePtr &b)
//
// LLL: Except for silent block files which have uninitialized filename.
if (fn.IsOk())
mBlockFileHash[fn.GetName()]=b;
mBlockFileHash[fn.GetName()] = b;
return b;
}
@ -1148,7 +1154,9 @@ BlockFilePtr DirManager::CopyBlockFile(const BlockFilePtr &b)
{
if( !wxCopyFile(fn.GetFullPath(),
newFile.GetFullPath()) )
return {};
// Disk space exhaustion, maybe
throw FileException{
FileException::Cause::Write, newFile };
}
// Done with fn
@ -1156,13 +1164,13 @@ BlockFilePtr DirManager::CopyBlockFile(const BlockFilePtr &b)
b2 = b->Copy(std::move(newFile));
if (b2 == NULL)
return {};
mBlockFileHash[newName]=b2;
mBlockFileHash[newName] = b2;
aliasList.Add(newPath);
}
if (!b2)
THROW_INCONSISTENCY_EXCEPTION;
return b2;
}

View File

@ -95,6 +95,8 @@ class PROFILE_DLL_API DirManager final : public XMLTagHandler {
// Adds one to the reference count of the block file,
// UNLESS it is "locked", then it makes a NEW copy of
// the BlockFile.
// May throw an exception in case of disk space exhaustion, otherwise
// returns non-null.
BlockFilePtr CopyBlockFile(const BlockFilePtr &b);
BlockFile *LoadBlockFile(const wxChar **attrs, sampleFormat format);

View File

@ -16,6 +16,7 @@
#include "Audacity.h"
#include "NoteTrack.h"
#include "Experimental.h"
#include <wx/dc.h>
#include <wx/brush.h>
@ -33,7 +34,7 @@
#include "Prefs.h"
#include "effects/TimeWarper.h"
#include "Experimental.h"
#include "InconsistencyException.h"
#ifdef SONIFY
#include "portmidi.h"
@ -435,8 +436,8 @@ int NoteTrack::GetVisibleChannels()
Track::Holder NoteTrack::Cut(double t0, double t1)
{
if (t1 <= t0)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
double len = t1-t0;
auto newTrack = std::make_unique<NoteTrack>(mDirManager);
@ -458,8 +459,8 @@ Track::Holder NoteTrack::Cut(double t0, double t1)
Track::Holder NoteTrack::Copy(double t0, double t1, bool) const
{
if (t1 <= t0)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
double len = t1-t0;
auto newTrack = std::make_unique<NoteTrack>(mDirManager);
@ -494,10 +495,9 @@ bool NoteTrack::Trim(double t0, double t1)
void NoteTrack::Clear(double t0, double t1)
{
// If t1 = t0, should Clear return true?
if (t1 <= t0)
// THROW_INCONSISTENCY_EXCEPTION; ?
return;
THROW_INCONSISTENCY_EXCEPTION;
double len = t1-t0;
if (mSeq)

View File

@ -441,8 +441,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
// PRL: Why bother with Internat when the above is just wxT?
Internat::ToString(s.as_double(), 0).c_str(),
Internat::ToString(mNumSamples.as_double(), 0).c_str());
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
}
// Quick check to make sure that it doesn't overflow
@ -453,8 +452,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
// PRL: Why bother with Internat when the above is just wxT?
Internat::ToString(mNumSamples.as_double(), 0).c_str(),
Internat::ToString(src->mNumSamples.as_double(), 0).c_str());
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
}
if (src->mSampleFormat != mSampleFormat)
@ -462,8 +460,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
wxLogError(
wxT("Sequence::Paste: Sample format to be pasted, %s, does not match destination format, %s."),
GetSampleFormatStr(src->mSampleFormat), GetSampleFormatStr(src->mSampleFormat));
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
}
const BlockArray &srcBlock = src->mBlock;
@ -607,11 +604,7 @@ void Sequence::Paste(sampleCount s, const Sequence *src)
for (i = 2; i < srcNumBlocks - 2; i++) {
const SeqBlock &block = srcBlock[i];
auto file = mDirManager->CopyBlockFile(block.f);
if (!file) {
wxASSERT(false); // TODO: Handle this better, alert the user of failure.
return;
}
// We can assume file is not null
newBlock.push_back(SeqBlock(file, block.start + s));
}
@ -645,8 +638,7 @@ void Sequence::InsertSilence(sampleCount s0, sampleCount len)
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + (len.as_double())))
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
if (len <= 0)
return;
@ -696,8 +688,7 @@ void Sequence::AppendAlias(const wxString &fullPath,
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + ((double)len)))
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
SeqBlock newBlock(
useOD?
@ -715,8 +706,7 @@ void Sequence::AppendCoded(const wxString &fName, sampleCount start,
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + ((double)len)))
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
SeqBlock newBlock(
mDirManager->NewODDecodeBlockFile(fName, start, len, channel, decodeType),
@ -732,19 +722,13 @@ void Sequence::AppendBlock
{
// Quick check to make sure that it doesn't overflow
if (Overflows((mNumSamples.as_double()) + ((double)b.f->GetLength())))
// THROW_INCONSISTENCY_EXCEPTION
return
;
THROW_INCONSISTENCY_EXCEPTION;
SeqBlock newBlock(
mDirManager.CopyBlockFile(b.f), // Bump ref count if not locked, else copy
mNumSamples
);
if (!newBlock.f) {
/// \todo Error Could not paste! (Out of disk space?)
wxASSERT(false); // TODO: Handle this better, alert the user of failure.
return;
}
// We can assume newBlock.f is not null
mBlock.push_back(newBlock);
mNumSamples += newBlock.f->GetLength();
@ -1137,8 +1121,7 @@ bool Sequence::Get(samplePtr buffer, sampleFormat format,
if (start < 0 || start > mNumSamples ||
start + len > mNumSamples) {
if (mayThrow)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
ClearSamples( buffer, floatSample, 0, len );
return false;
}
@ -1176,8 +1159,7 @@ void Sequence::SetSamples(samplePtr buffer, sampleFormat format,
{
if (start < 0 || start >= mNumSamples ||
start + len > mNumSamples)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
SampleBuffer scratch(mMaxSamples, mSampleFormat);
@ -1213,9 +1195,7 @@ void Sequence::SetSamples(samplePtr buffer, sampleFormat format,
if (!(fileLength <= mMaxSamples &&
bstart + blen <= fileLength))
//THROW_INCONSISTENCY_EXCEPTION
wxASSERT(false)
;
THROW_INCONSISTENCY_EXCEPTION;
if ( bstart > 0 || blen < fileLength ) {
Read(scratch.ptr(), mSampleFormat, block, 0, fileLength, true);
@ -1527,8 +1507,7 @@ void Sequence::Append(samplePtr buffer, sampleFormat format,
// Quick check to make sure that it doesn't overflow
if (Overflows(mNumSamples.as_double() + ((double)len)))
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
BlockArray newBlock;
sampleCount newNumSamples = mNumSamples;
@ -1644,8 +1623,7 @@ void Sequence::Delete(sampleCount start, sampleCount len)
return;
if (len < 0 || start < 0 || start >= mNumSamples)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
//TODO: add a ref-deref mechanism to SeqBlock/BlockArray so we don't have to make this a critical section.
//On-demand threads iterate over the mBlocks and the GUI thread deletes them, so for now put a mutex here over
@ -1858,8 +1836,7 @@ void Sequence::ConsistencyCheck
wxT("Undo the failed operation(s), then export or save your work and quit."));
if (mayThrow)
//throw ex
;
throw ex;
else
wxASSERT(false);
}

View File

@ -103,6 +103,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{
bool GetWaveDisplay(float *min, float *max, float *rms, int* bl,
size_t len, const sampleCount *where) const;
// Return non-null, or else throw!
std::unique_ptr<Sequence> Copy(sampleCount s0, sampleCount s1) const;
void Paste(sampleCount s0, const Sequence *src);

View File

@ -1298,8 +1298,7 @@ std::pair<float, float> WaveClip::GetMinMax(
{
if (t0 > t1) {
if (mayThrow)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
return {
0.f, // harmless, but unused since Sequence::GetMinMax does not use these values
0.f // harmless, but unused since Sequence::GetMinMax does not use these values
@ -1321,8 +1320,7 @@ float WaveClip::GetRMS(double t0, double t1, bool mayThrow) const
{
if (t0 > t1) {
if (mayThrow)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
return 0.f;
}
@ -1783,10 +1781,8 @@ void WaveClip::ExpandCutLine(double cutLinePosition)
[=](const WaveClipHolder &p) { return p.get() == cutline; });
if (it != end)
mCutLines.erase(it); // deletes cutline!
else {
// THROW_INCONSISTENCY_EXCEPTION;
wxASSERT(false);
}
else
THROW_INCONSISTENCY_EXCEPTION;
}
}
@ -1906,13 +1902,14 @@ void WaveClip::Resample(int rate, ProgressDialog *progress)
);
error = (updateResult != ProgressResult::Success);
if (error)
break;
//throw UserException{};
throw UserException{};
}
}
if (error)
;
throw SimpleMessageBoxException{
_("Resampling failed.")
};
else
{
// Use NOFAIL-GUARANTEE in these steps

View File

@ -555,8 +555,7 @@ bool WaveTrack::IsEmpty(double t0, double t1) const
Track::Holder WaveTrack::Cut(double t0, double t1)
{
if (t1 < t0)
// THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
auto tmp = Copy(t0, t1);
@ -569,8 +568,7 @@ Track::Holder WaveTrack::SplitCut(double t0, double t1)
// STRONG-GUARANTEE
{
if (t1 < t0)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
// SplitCut is the same as 'Copy', then 'SplitDelete'
auto tmp = Copy(t0, t1);
@ -584,15 +582,12 @@ Track::Holder WaveTrack::SplitCut(double t0, double t1)
Track::Holder WaveTrack::CutAndAddCutLine(double t0, double t1)
{
if (t1 < t0)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
// Cut is the same as 'Copy', then 'Delete'
auto tmp = Copy(t0, t1);
if (!ClearAndAddCutLine(t0, t1))
//THROW_INCONSISTENCY_EXCEPTION
;
ClearAndAddCutLine(t0, t1);
return tmp;
}
@ -648,8 +643,7 @@ void WaveTrack::Trim (double t0, double t1)
Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
{
if (t1 <= t0)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
WaveTrack *newTrack;
Track::Holder result
@ -1038,8 +1032,7 @@ void WaveTrack::HandleClear(double t0, double t1,
// STRONG-GUARANTEE
{
if (t1 < t0)
// THROW_INCONSISTENCY_EXCEPTION; // ?
return;
THROW_INCONSISTENCY_EXCEPTION;
bool editClipCanMove = true;
gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove);
@ -1221,12 +1214,10 @@ void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
// follow EditClipCanMove rules (Paste() does it right)
AudacityProject *p = GetActiveProject();
if (!p)
// THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
TrackFactory *f = p->GetTrackFactory();
if (!f)
// THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
auto tmp = f->NewWaveTrack(GetSampleFormat(), GetRate());
tmp->InsertSilence(0.0, newT1 - oldT1);
@ -1395,8 +1386,7 @@ void WaveTrack::Paste(double t0, const Track *src)
void WaveTrack::Silence(double t0, double t1)
{
if (t1 < t0)
// THROW_INCONSISTENCY_EXCEPTION; // ?
return;
THROW_INCONSISTENCY_EXCEPTION;
auto start = (sampleCount)floor(t0 * mRate + 0.5);
auto len = (sampleCount)floor(t1 * mRate + 0.5) - start;
@ -1432,8 +1422,7 @@ void WaveTrack::InsertSilence(double t, double len)
// STRONG-GUARANTEE
{
if (len <= 0)
// THROW_INCONSISTENCY_EXCEPTION; // ?
return;
THROW_INCONSISTENCY_EXCEPTION;
if (mClips.empty())
{
@ -1953,8 +1942,7 @@ std::pair<float, float> WaveTrack::GetMinMax(
if (t0 > t1) {
if (mayThrow)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
return results;
}
@ -1986,8 +1974,7 @@ float WaveTrack::GetRMS(double t0, double t1, bool mayThrow) const
{
if (t0 > t1) {
if (mayThrow)
//THROW_INCONSISTENCY_EXCEPTION
;
THROW_INCONSISTENCY_EXCEPTION;
return 0.f;
}

View File

@ -27,6 +27,7 @@ The summary is eventually computed and written to a file in a background thread.
#include <wx/thread.h>
#include <sndfile.h>
#include "../FileException.h"
#include "../FileFormats.h"
#include "../Internat.h"
#include "NotYetAvailableException.h"
@ -38,8 +39,9 @@ char bheaderTag[bheaderTagLen + 1] = "AudacityBlockFile112";
/// Create a disk file and write summary and sample data to it
ODDecodeBlockFile::ODDecodeBlockFile(wxFileNameWrapper &&baseFileName, wxFileNameWrapper &&audioFileName, sampleCount aliasStart,
size_t aliasLen, int aliasChannel,unsigned int decodeType):
SimpleBlockFile{ std::move(baseFileName),
size_t aliasLen, int aliasChannel,unsigned int decodeType)
try
: SimpleBlockFile{ std::move(baseFileName),
NULL, aliasLen, floatSample, true, true },
//floatSample has no effect. last two bools - bypass writing of blockfile and cache
@ -51,6 +53,14 @@ ODDecodeBlockFile::ODDecodeBlockFile(wxFileNameWrapper &&baseFileName, wxFileNam
mAudioFileName = std::move(audioFileName);
mFormat = int16Sample;
}
catch ( const FileException & e )
{
// The constructor SimpleBlockFile sometimes throws this,
// but it never will for the arguments that were passed to it here.
// So add a catch for completeness, but just assert that this won't happen.
wxASSERT(false);
throw;
}
/// Create the memory structure to refer to the given block file
ODDecodeBlockFile::ODDecodeBlockFile(wxFileNameWrapper &&existingFile, wxFileNameWrapper &&audioFileName, sampleCount aliasStart,
@ -102,8 +112,7 @@ auto ODDecodeBlockFile::GetMinMaxRMS(
else
{
if (mayThrow)
// throw NotYetAvailableException{ mAudioFileName }
;
throw NotYetAvailableException{ mAudioFileName };
//fake values. These values are used usually for normalization and amplifying, so we want
//the max to be maximal and the min to be minimal
@ -123,8 +132,7 @@ auto ODDecodeBlockFile::GetMinMaxRMS(bool mayThrow) const -> MinMaxRMS
else
{
if (mayThrow)
// throw NotYetAvailableException{ mAudioFileName }
;
throw NotYetAvailableException{ mAudioFileName };
//fake values. These values are used usually for normalization and amplifying, so we want
//the max to be maximal and the min to be minimal
@ -446,8 +454,7 @@ size_t ODDecodeBlockFile::ReadData(samplePtr data, sampleFormat format,
else
{
if (mayThrow)
//throw NotYetAvailableException{ mFileName }
;
throw NotYetAvailableException{ mAudioFileName };
//we should do an ODRequest to start processing the data here, and wait till it finishes. and just do a SimpleBlockFile
//ReadData.

View File

@ -133,8 +133,7 @@ auto ODPCMAliasBlockFile::GetMinMaxRMS(
else
{
if (mayThrow)
//throw NotYetAvailableException{ GetAliasedFileName() }
;
throw NotYetAvailableException{ GetAliasedFileName() };
//fake values. These values are used usually for normalization and amplifying, so we want
//the max to be maximal and the min to be minimal
@ -156,8 +155,7 @@ auto ODPCMAliasBlockFile::GetMinMaxRMS(bool mayThrow) const -> MinMaxRMS
else
{
if (mayThrow)
//throw NotYetAvailableException{ GetAliasedFileName() }
;
throw NotYetAvailableException{ GetAliasedFileName() };
//fake values. These values are used usually for normalization and amplifying, so we want
//the max to be maximal and the min to be minimal
@ -415,9 +413,8 @@ void ODPCMAliasBlockFile::WriteSummary()
//and wxLog calls are not thread safe.
printf("Unable to write summary data to file: %s", fileNameChar.get());
// throw FileException{
// FileException::Cause::Read, wxFileName{ fileNameChar.get() } };
return;
throw FileException{
FileException::Cause::Read, wxFileName{ fileNameChar.get() } };
}
ArrayOf<char> cleanup;

View File

@ -57,15 +57,18 @@ to get its definition, rather than rolling our own.
*//*******************************************************************/
#include "../Audacity.h"
#include "SimpleBlockFile.h"
#include <wx/wx.h>
#include <wx/filefn.h>
#include <wx/ffile.h>
#include <wx/utils.h>
#include <wx/log.h>
#include "../FileException.h"
#include "../Prefs.h"
#include "SimpleBlockFile.h"
#include "../FileFormats.h"
#include "sndfile.h"
@ -114,8 +117,9 @@ SimpleBlockFile::SimpleBlockFile(wxFileNameWrapper &&baseFileName,
if (!(allowDeferredWrite && useCache) && !bypassCache)
{
bool bSuccess = WriteSimpleBlockFile(sampleData, sampleLen, format, NULL);
wxASSERT(bSuccess); // TODO: Handle failure here by alert to user and undo partial op.
wxUnusedVar(bSuccess);
if (!bSuccess)
throw FileException{
FileException::Cause::Write, GetFileName().name };
}
if (useCache) {
@ -404,8 +408,7 @@ size_t SimpleBlockFile::ReadData(samplePtr data, sampleFormat format,
if ( framesRead < len ) {
if (mayThrow)
// Not the best exception class?
//throw FileException{ FileException::Cause::Read, mFileName }
;
throw FileException{ FileException::Cause::Read, mFileName };
ClearSamples(data, format, framesRead, len - framesRead);
}

View File

@ -27,10 +27,12 @@ and sample size to help you importing data of an unknown format.
#include "Import.h"
#include "../DirManager.h"
#include "../FileException.h"
#include "../FileFormats.h"
#include "../Internat.h"
#include "../Prefs.h"
#include "../ShuttleGui.h"
#include "../UserException.h"
#include "../WaveTrack.h"
#include <cmath>
@ -92,6 +94,9 @@ class ImportRawDialog final : public wxDialogWrapper {
DECLARE_EVENT_TABLE()
};
// This function leaves outTracks empty as an indication of error,
// but may also throw FileException to make use of the application's
// user visible error reporting.
void ImportRaw(wxWindow *parent, const wxString &fileName,
TrackFactory *trackFactory, TrackHolders &outTracks)
{
@ -158,12 +163,11 @@ void ImportRaw(wxWindow *parent, const wxString &fileName,
}
if (!sndFile){
// TODO: Handle error
char str[1000];
sf_error_str((SNDFILE *)NULL, str, 1000);
printf("%s\n", str);
return;
throw FileException{ FileException::Cause::Open, fileName };
}
result = sf_command(sndFile.get(), SFC_SET_RAW_START_OFFSET, &offset, sizeof(offset));
@ -171,6 +175,8 @@ void ImportRaw(wxWindow *parent, const wxString &fileName,
char str[1000];
sf_error_str(sndFile.get(), str, 1000);
printf("%s\n", str);
throw FileException{ FileException::Cause::Read, fileName };
}
SFCall<sf_count_t>(sf_seek, sndFile.get(), 0, SEEK_SET);
@ -254,9 +260,7 @@ void ImportRaw(wxWindow *parent, const wxString &fileName,
else {
// This is not supposed to happen, sndfile.h says result is always
// a count, not an invalid value for error
wxASSERT(false);
updateResult = ProgressResult::Failed;
break;
throw FileException{ FileException::Cause::Read, fileName };
}
if (block) {
@ -288,10 +292,8 @@ void ImportRaw(wxWindow *parent, const wxString &fileName,
} while (block > 0 && framescompleted < totalFrames);
}
if (updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled) {
// It's a shame we can't return proper error code
return;
}
if (updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled)
throw UserException{};
for (const auto &channel : channels)
channel->Flush();