From 104d40c2d90863902ef981585197945b8fa3b11b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 12:55:54 +0100 Subject: [PATCH 01/10] Add a CPLErrorAccumulator class, typically used by a worker thread to store errors emitted by their worker functions --- port/cpl_error.cpp | 62 ++++++++++++++++++++++++++++++++++++--- port/cpl_error_internal.h | 50 ++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/port/cpl_error.cpp b/port/cpl_error.cpp index 79296abe7fb6..522cb9c97158 100644 --- a/port/cpl_error.cpp +++ b/port/cpl_error.cpp @@ -1536,11 +1536,11 @@ bool CPLIsDefaultErrorHandlerAndCatchDebug() } /************************************************************************/ -/* CPLErrorHandlerAccumulator() */ +/* CPLErrorAccumulatorFunc() */ /************************************************************************/ -static void CPL_STDCALL CPLErrorHandlerAccumulator(CPLErr eErr, CPLErrorNum no, - const char *msg) +static void CPL_STDCALL CPLErrorAccumulatorFunc(CPLErr eErr, CPLErrorNum no, + const char *msg) { std::vector *paoErrors = static_cast *>( @@ -1551,7 +1551,7 @@ static void CPL_STDCALL CPLErrorHandlerAccumulator(CPLErr eErr, CPLErrorNum no, void CPLInstallErrorHandlerAccumulator( std::vector &aoErrors) { - CPLPushErrorHandlerEx(CPLErrorHandlerAccumulator, &aoErrors); + CPLPushErrorHandlerEx(CPLErrorAccumulatorFunc, &aoErrors); } void CPLUninstallErrorHandlerAccumulator() @@ -1583,3 +1583,57 @@ CPLErrorStateBackuper::~CPLErrorStateBackuper() CPLErrorSetState(m_nLastErrorType, m_nLastErrorNum, m_osLastErrorMsg.c_str(), &m_nLastErrorCounter); } + +/*! @cond Doxygen_Suppress */ + +/************************************************************************/ +/* CPLErrorAccumulator::Context::~Context() */ +/************************************************************************/ + +CPLErrorAccumulator::Context::~Context() +{ + CPLPopErrorHandler(); +} + +/************************************************************************/ +/* CPLErrorAccumulator::InstallForCurrentScope() */ +/************************************************************************/ + +CPLErrorAccumulator::Context CPLErrorAccumulator::InstallForCurrentScope() +{ + CPLPushErrorHandlerEx(CPLErrorAccumulator::Accumulator, this); + return CPLErrorAccumulator::Context(); +} + +/************************************************************************/ +/* CPLErrorAccumulator::ReplayErrors() */ +/************************************************************************/ + +void CPLErrorAccumulator::ReplayErrors() +{ + std::lock_guard oLock(mutex); + for (const auto &err : errors) + { + CPLError(err.type, err.no, "%s", err.msg.c_str()); + } +} + +/************************************************************************/ +/* CPLErrorAccumulator::Accumulator() */ +/************************************************************************/ + +/* static */ void CPL_STDCALL CPLErrorAccumulator::Accumulator(CPLErr eErr, + CPLErrorNum no, + const char *msg) +{ + if (eErr != CE_Debug) + { + CPLErrorAccumulator *pThis = + static_cast(CPLGetErrorHandlerUserData()); + std::lock_guard oLock(pThis->mutex); + pThis->errors.push_back( + CPLErrorHandlerAccumulatorStruct(eErr, no, msg)); + } +} + +/*! @endcond */ diff --git a/port/cpl_error_internal.h b/port/cpl_error_internal.h index 2b8f27913dfa..921727d07f3c 100644 --- a/port/cpl_error_internal.h +++ b/port/cpl_error_internal.h @@ -19,10 +19,12 @@ #include "cpl_error.h" #include "cpl_string.h" + +#include #include /************************************************************************/ -/* ErrorHandlerAccumulator() */ +/* CPLErrorHandlerAccumulatorStruct */ /************************************************************************/ class CPL_DLL CPLErrorHandlerAccumulatorStruct @@ -47,6 +49,52 @@ void CPL_DLL CPLInstallErrorHandlerAccumulator( std::vector &aoErrors); void CPL_DLL CPLUninstallErrorHandlerAccumulator(); +/************************************************************************/ +/* CPLErrorAccumulator */ +/************************************************************************/ + +/** Class typically used by a worker thread to store errors emitted by their + * worker functions, and replay them in the main thread. + * + * An instance of CPLErrorAccumulator can be shared by several + * threads. Each thread calls InstallForCurrentScope() in its processing + * function. The main thread may invoke ReplayErrors() to replay errors (and + * warnings). + * + * @since 3.11 + */ +class CPL_DLL CPLErrorAccumulator +{ + public: + /** Constructor */ + CPLErrorAccumulator() = default; + + struct Context + { + ~Context(); + }; + + /** Install a temporary error handler that will store errors and warnings. + */ + Context InstallForCurrentScope() CPL_WARN_UNUSED_RESULT; + + /** Return error list. */ + const std::vector &GetErrors() const + { + return errors; + } + + /** Replay stored errors. */ + void ReplayErrors(); + + private: + std::mutex mutex{}; + std::vector errors{}; + + static void CPL_STDCALL Accumulator(CPLErr eErr, CPLErrorNum no, + const char *msg); +}; + #endif #endif // CPL_ERROR_INTERNAL_H_INCLUDED From 9ceea7e56f0173628d08f40bfc778cb721831123 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 12:56:21 +0100 Subject: [PATCH 02/10] VRT: RasterIO(): propagate errors from worker thread to main thread Fixes #11904 --- autotest/gcore/vrt_read.py | 27 ++++++ frmts/vrt/vrtdataset.cpp | 100 ++++++++++++++++------- frmts/vrt/vrtdataset.h | 55 +------------ frmts/vrt/vrtsourcedrasterband.cpp | 127 +++++++++++++++++++---------- port/cpl_error_internal.h | 2 +- 5 files changed, 182 insertions(+), 129 deletions(-) diff --git a/autotest/gcore/vrt_read.py b/autotest/gcore/vrt_read.py index 516298a22073..7543cb2ebbad 100755 --- a/autotest/gcore/vrt_read.py +++ b/autotest/gcore/vrt_read.py @@ -2805,6 +2805,33 @@ def test_vrt_read_multi_threaded_disabled_since_overlapping_sources(): ) +############################################################################### +# Test propagation of errors from threads to main thread in multi-threaded reading + + +@gdaltest.enable_exceptions() +def test_vrt_read_multi_threaded_errors(tmp_vsimem): + + filename1 = str(tmp_vsimem / "tmp1.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename1, 1, 1) + ds.SetGeoTransform([2, 1, 0, 49, 0, -1]) + ds.Close() + + filename2 = str(tmp_vsimem / "tmp2.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename2, 1, 1) + ds.SetGeoTransform([3, 1, 0, 49, 0, -1]) + ds.Close() + + vrt_filename = str(tmp_vsimem / "tmp.vrt") + gdal.BuildVRT(vrt_filename, [filename1, filename2]) + + gdal.Unlink(filename2) + + with gdal.Open(vrt_filename) as ds: + with pytest.raises(Exception): + ds.GetRasterBand(1).ReadRaster() + + ############################################################################### # Test reading a VRT with a inside a diff --git a/frmts/vrt/vrtdataset.cpp b/frmts/vrt/vrtdataset.cpp index b8b7df6a16fd..5cb60156f63c 100644 --- a/frmts/vrt/vrtdataset.cpp +++ b/frmts/vrt/vrtdataset.cpp @@ -13,6 +13,7 @@ #include "vrtdataset.h" +#include "cpl_error_internal.h" #include "cpl_minixml.h" #include "cpl_string.h" #include "gdal_frmts.h" @@ -2236,6 +2237,70 @@ CPLErr VRTDataset::AdviseRead(int nXOff, int nYOff, int nXSize, int nYSize, return std::min(atoi(pszNumThreads), nLimit); } +/************************************************************************/ +/* VRTDatasetRasterIOJob */ +/************************************************************************/ + +/** Structure used to declare a threaded job to satisfy IRasterIO() + * on a given source. + */ +struct VRTDatasetRasterIOJob +{ + std::atomic *pnCompletedJobs = nullptr; + std::atomic *pbSuccess = nullptr; + CPLErrorAccumulator *poErrorAccumulator = nullptr; + + GDALDataType eVRTBandDataType = GDT_Unknown; + int nXOff = 0; + int nYOff = 0; + int nXSize = 0; + int nYSize = 0; + void *pData = nullptr; + int nBufXSize = 0; + int nBufYSize = 0; + int nBandCount = 0; + BANDMAP_TYPE panBandMap = nullptr; + GDALDataType eBufType = GDT_Unknown; + GSpacing nPixelSpace = 0; + GSpacing nLineSpace = 0; + GSpacing nBandSpace = 0; + GDALRasterIOExtraArg *psExtraArg = nullptr; + VRTSimpleSource *poSource = nullptr; + + static void Func(void *pData); +}; + +/************************************************************************/ +/* VRTDatasetRasterIOJob::Func() */ +/************************************************************************/ + +void VRTDatasetRasterIOJob::Func(void *pData) +{ + auto psJob = std::unique_ptr( + static_cast(pData)); + if (*psJob->pbSuccess) + { + GDALRasterIOExtraArg sArg = *(psJob->psExtraArg); + sArg.pfnProgress = nullptr; + sArg.pProgressData = nullptr; + + auto oAccumulator = psJob->poErrorAccumulator->InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + + if (psJob->poSource->DatasetRasterIO( + psJob->eVRTBandDataType, psJob->nXOff, psJob->nYOff, + psJob->nXSize, psJob->nYSize, psJob->pData, psJob->nBufXSize, + psJob->nBufYSize, psJob->eBufType, psJob->nBandCount, + psJob->panBandMap, psJob->nPixelSpace, psJob->nLineSpace, + psJob->nBandSpace, &sArg) != CE_None) + { + *psJob->pbSuccess = false; + } + } + + ++(*psJob->pnCompletedJobs); +} + /************************************************************************/ /* IRasterIO() */ /************************************************************************/ @@ -2378,6 +2443,7 @@ CPLErr VRTDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, m_bMultiThreadedRasterIOLastUsed = true; m_oMapSharedSources.InitMutex(); + CPLErrorAccumulator errorAccumulator; std::atomic bSuccess = true; CPLWorkerThreadPool *psThreadPool = GDALGetGlobalThreadPool( std::min(nContributingSources, nMaxThreads)); @@ -2401,8 +2467,9 @@ CPLErr VRTDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, if (poSimpleSource->DstWindowIntersects(dfXOff, dfYOff, dfXSize, dfYSize)) { - auto psJob = new RasterIOJob(); + auto psJob = new VRTDatasetRasterIOJob(); psJob->pbSuccess = &bSuccess; + psJob->poErrorAccumulator = &errorAccumulator; psJob->pnCompletedJobs = &nCompletedJobs; psJob->eVRTBandDataType = poBand->GetRasterDataType(); psJob->nXOff = nXOff; @@ -2421,7 +2488,7 @@ CPLErr VRTDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, psJob->psExtraArg = psExtraArg; psJob->poSource = poSimpleSource; - if (!oQueue->SubmitJob(RasterIOJob::Func, psJob)) + if (!oQueue->SubmitJob(VRTDatasetRasterIOJob::Func, psJob)) { delete psJob; bSuccess = false; @@ -2442,6 +2509,7 @@ CPLErr VRTDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, } } + errorAccumulator.ReplayErrors(); eErr = bSuccess ? CE_None : CE_Failure; } else @@ -2505,34 +2573,6 @@ CPLErr VRTDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, return eErr; } -/************************************************************************/ -/* VRTDataset::RasterIOJob::Func() */ -/************************************************************************/ - -void VRTDataset::RasterIOJob::Func(void *pData) -{ - auto psJob = - std::unique_ptr(static_cast(pData)); - if (*psJob->pbSuccess) - { - GDALRasterIOExtraArg sArg = *(psJob->psExtraArg); - sArg.pfnProgress = nullptr; - sArg.pProgressData = nullptr; - - if (psJob->poSource->DatasetRasterIO( - psJob->eVRTBandDataType, psJob->nXOff, psJob->nYOff, - psJob->nXSize, psJob->nYSize, psJob->pData, psJob->nBufXSize, - psJob->nBufYSize, psJob->eBufType, psJob->nBandCount, - psJob->panBandMap, psJob->nPixelSpace, psJob->nLineSpace, - psJob->nBandSpace, &sArg) != CE_None) - { - *psJob->pbSuccess = false; - } - } - - ++(*psJob->pnCompletedJobs); -} - /************************************************************************/ /* UnsetPreservedRelativeFilenames() */ /************************************************************************/ diff --git a/frmts/vrt/vrtdataset.h b/frmts/vrt/vrtdataset.h index daac98c71e7f..b827eb036f30 100644 --- a/frmts/vrt/vrtdataset.h +++ b/frmts/vrt/vrtdataset.h @@ -245,6 +245,7 @@ class CPL_DLL VRTDataset CPL_NON_FINAL : public GDALDataset friend struct VRTFlushCacheStruct; friend class VRTSourcedRasterBand; friend class VRTSimpleSource; + friend struct VRTSourcedRasterBandRasterIOJob; friend VRTDatasetH CPL_STDCALL VRTCreate(int nXSize, int nYSize); std::vector m_asGCPs{}; @@ -303,34 +304,6 @@ class CPL_DLL VRTDataset CPL_NON_FINAL : public GDALDataset GDALDataset *&poSrcDataset, int &nSrcXOff, int &nSrcYOff); - /** Structure used to declare a threaded job to satisfy IRasterIO() - * on a given source. - */ - struct RasterIOJob - { - std::atomic *pnCompletedJobs = nullptr; - std::atomic *pbSuccess = nullptr; - - GDALDataType eVRTBandDataType = GDT_Unknown; - int nXOff = 0; - int nYOff = 0; - int nXSize = 0; - int nYSize = 0; - void *pData = nullptr; - int nBufXSize = 0; - int nBufYSize = 0; - int nBandCount = 0; - BANDMAP_TYPE panBandMap = nullptr; - GDALDataType eBufType = GDT_Unknown; - GSpacing nPixelSpace = 0; - GSpacing nLineSpace = 0; - GSpacing nBandSpace = 0; - GDALRasterIOExtraArg *psExtraArg = nullptr; - VRTSimpleSource *poSource = nullptr; - - static void Func(void *pData); - }; - CPL_DISALLOW_COPY_ASSIGN(VRTDataset) protected: @@ -958,32 +931,6 @@ class CPL_DLL VRTSourcedRasterBand CPL_NON_FINAL : public VRTRasterBand bool IsMosaicOfNonOverlappingSimpleSourcesOfFullRasterNoResAndTypeChange( bool bAllowMaxValAdjustment) const; - /** Structure used to declare a threaded job to satisfy IRasterIO() - * on a given source. - */ - struct RasterIOJob - { - std::atomic *pnCompletedJobs = nullptr; - std::atomic *pbSuccess = nullptr; - VRTDataset::QueueWorkingStates *poQueueWorkingStates = nullptr; - - GDALDataType eVRTBandDataType = GDT_Unknown; - int nXOff = 0; - int nYOff = 0; - int nXSize = 0; - int nYSize = 0; - void *pData = nullptr; - int nBufXSize = 0; - int nBufYSize = 0; - GDALDataType eBufType = GDT_Unknown; - GSpacing nPixelSpace = 0; - GSpacing nLineSpace = 0; - GDALRasterIOExtraArg *psExtraArg = nullptr; - VRTSimpleSource *poSource = nullptr; - - static void Func(void *pData); - }; - CPL_DISALLOW_COPY_ASSIGN(VRTSourcedRasterBand) protected: diff --git a/frmts/vrt/vrtsourcedrasterband.cpp b/frmts/vrt/vrtsourcedrasterband.cpp index 05bd3111979c..9fe38a83bfcf 100644 --- a/frmts/vrt/vrtsourcedrasterband.cpp +++ b/frmts/vrt/vrtsourcedrasterband.cpp @@ -29,6 +29,7 @@ #include "cpl_conv.h" #include "cpl_error.h" +#include "cpl_error_internal.h" #include "cpl_hash_set.h" #include "cpl_minixml.h" #include "cpl_progress.h" @@ -363,6 +364,82 @@ bool VRTSourcedRasterBand::CanMultiThreadRasterIO( return bRet; } +/************************************************************************/ +/* VRTSourcedRasterBandRasterIOJob */ +/************************************************************************/ + +/** Structure used to declare a threaded job to satisfy IRasterIO() + * on a given source. + */ +struct VRTSourcedRasterBandRasterIOJob +{ + std::atomic *pnCompletedJobs = nullptr; + std::atomic *pbSuccess = nullptr; + VRTDataset::QueueWorkingStates *poQueueWorkingStates = nullptr; + CPLErrorAccumulator *poErrorAccumulator = nullptr; + + GDALDataType eVRTBandDataType = GDT_Unknown; + int nXOff = 0; + int nYOff = 0; + int nXSize = 0; + int nYSize = 0; + void *pData = nullptr; + int nBufXSize = 0; + int nBufYSize = 0; + GDALDataType eBufType = GDT_Unknown; + GSpacing nPixelSpace = 0; + GSpacing nLineSpace = 0; + GDALRasterIOExtraArg *psExtraArg = nullptr; + VRTSimpleSource *poSource = nullptr; + + static void Func(void *pData); +}; + +/************************************************************************/ +/* VRTSourcedRasterBandRasterIOJob::Func() */ +/************************************************************************/ + +void VRTSourcedRasterBandRasterIOJob::Func(void *pData) +{ + auto psJob = std::unique_ptr( + static_cast(pData)); + if (*psJob->pbSuccess) + { + GDALRasterIOExtraArg sArg = *(psJob->psExtraArg); + sArg.pfnProgress = nullptr; + sArg.pProgressData = nullptr; + + std::unique_ptr poWorkingState; + { + std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex); + poWorkingState = + std::move(psJob->poQueueWorkingStates->oStates.back()); + psJob->poQueueWorkingStates->oStates.pop_back(); + CPLAssert(poWorkingState.get()); + } + + auto oAccumulator = psJob->poErrorAccumulator->InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + + if (psJob->poSource->RasterIO( + psJob->eVRTBandDataType, psJob->nXOff, psJob->nYOff, + psJob->nXSize, psJob->nYSize, psJob->pData, psJob->nBufXSize, + psJob->nBufYSize, psJob->eBufType, psJob->nPixelSpace, + psJob->nLineSpace, &sArg, *(poWorkingState.get())) != CE_None) + { + *psJob->pbSuccess = false; + } + + { + std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex); + psJob->poQueueWorkingStates->oStates.push_back( + std::move(poWorkingState)); + } + } + + ++(*psJob->pnCompletedJobs); +} + /************************************************************************/ /* IRasterIO() */ /************************************************************************/ @@ -565,6 +642,7 @@ CPLErr VRTSourcedRasterBand::IRasterIO( l_poDS->m_bMultiThreadedRasterIOLastUsed = true; l_poDS->m_oMapSharedSources.InitMutex(); + CPLErrorAccumulator errorAccumulator; std::atomic bSuccess = true; CPLWorkerThreadPool *psThreadPool = GDALGetGlobalThreadPool( std::min(nContributingSources, nMaxThreads)); @@ -602,10 +680,11 @@ CPLErr VRTSourcedRasterBand::IRasterIO( if (poSimpleSource->DstWindowIntersects(dfXOff, dfYOff, dfXSize, dfYSize)) { - auto psJob = new RasterIOJob(); + auto psJob = new VRTSourcedRasterBandRasterIOJob(); psJob->pbSuccess = &bSuccess; psJob->pnCompletedJobs = &nCompletedJobs; psJob->poQueueWorkingStates = &(l_poDS->m_oQueueWorkingStates); + psJob->poErrorAccumulator = &errorAccumulator; psJob->eVRTBandDataType = eDataType; psJob->nXOff = nXOff; psJob->nYOff = nYOff; @@ -620,7 +699,8 @@ CPLErr VRTSourcedRasterBand::IRasterIO( psJob->psExtraArg = psExtraArg; psJob->poSource = poSimpleSource; - if (!oQueue->SubmitJob(RasterIOJob::Func, psJob)) + if (!oQueue->SubmitJob(VRTSourcedRasterBandRasterIOJob::Func, + psJob)) { delete psJob; bSuccess = false; @@ -641,6 +721,7 @@ CPLErr VRTSourcedRasterBand::IRasterIO( } } + errorAccumulator.ReplayErrors(); eErr = bSuccess ? CE_None : CE_Failure; } else @@ -678,48 +759,6 @@ CPLErr VRTSourcedRasterBand::IRasterIO( return eErr; } -/************************************************************************/ -/* VRTSourcedRasterBand::RasterIOJob::Func() */ -/************************************************************************/ - -void VRTSourcedRasterBand::RasterIOJob::Func(void *pData) -{ - auto psJob = - std::unique_ptr(static_cast(pData)); - if (*psJob->pbSuccess) - { - GDALRasterIOExtraArg sArg = *(psJob->psExtraArg); - sArg.pfnProgress = nullptr; - sArg.pProgressData = nullptr; - - std::unique_ptr poWorkingState; - { - std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex); - poWorkingState = - std::move(psJob->poQueueWorkingStates->oStates.back()); - psJob->poQueueWorkingStates->oStates.pop_back(); - CPLAssert(poWorkingState.get()); - } - - if (psJob->poSource->RasterIO( - psJob->eVRTBandDataType, psJob->nXOff, psJob->nYOff, - psJob->nXSize, psJob->nYSize, psJob->pData, psJob->nBufXSize, - psJob->nBufYSize, psJob->eBufType, psJob->nPixelSpace, - psJob->nLineSpace, &sArg, *(poWorkingState.get())) != CE_None) - { - *psJob->pbSuccess = false; - } - - { - std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex); - psJob->poQueueWorkingStates->oStates.push_back( - std::move(poWorkingState)); - } - } - - ++(*psJob->pnCompletedJobs); -} - /************************************************************************/ /* IGetDataCoverageStatus() */ /************************************************************************/ diff --git a/port/cpl_error_internal.h b/port/cpl_error_internal.h index 921727d07f3c..3fcff32952b3 100644 --- a/port/cpl_error_internal.h +++ b/port/cpl_error_internal.h @@ -69,7 +69,7 @@ class CPL_DLL CPLErrorAccumulator /** Constructor */ CPLErrorAccumulator() = default; - struct Context + struct CPL_DLL Context { ~Context(); }; From fe2f89deee36aef9a000449d96116a1c7620c2b1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 12:56:52 +0100 Subject: [PATCH 03/10] Multi-threaded warp: propagate errors from worker thread to main thread Fixes #11904 --- alg/gdalwarpoperation.cpp | 49 +++++++++++++++++++++++---------------- autotest/alg/warp.py | 27 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/alg/gdalwarpoperation.cpp b/alg/gdalwarpoperation.cpp index fc0bbe9f2956..c080b932924d 100644 --- a/alg/gdalwarpoperation.cpp +++ b/alg/gdalwarpoperation.cpp @@ -29,6 +29,7 @@ #include "cpl_config.h" #include "cpl_conv.h" #include "cpl_error.h" +#include "cpl_error_internal.h" #include "cpl_mask.h" #include "cpl_multiproc.h" #include "cpl_string.h" @@ -1044,20 +1045,22 @@ CPLErr GDALChunkAndWarpImage(GDALWarpOperationH hOperation, int nDstXOff, /* ChunkThreadMain() */ /************************************************************************/ -typedef struct +struct ChunkThreadData { - GDALWarpOperation *poOperation; - GDALWarpChunk *pasChunkInfo; - CPLJoinableThread *hThreadHandle; - CPLErr eErr; - double dfProgressBase; - double dfProgressScale; - CPLMutex *hIOMutex; - - CPLMutex *hCondMutex; - volatile int bIOMutexTaken; - CPLCond *hCond; -} ChunkThreadData; + GDALWarpOperation *poOperation = nullptr; + GDALWarpChunk *pasChunkInfo = nullptr; + CPLJoinableThread *hThreadHandle = nullptr; + CPLErr eErr = CE_None; + double dfProgressBase = 0; + double dfProgressScale = 0; + CPLMutex *hIOMutex = nullptr; + + CPLMutex *hCondMutex = nullptr; + volatile int bIOMutexTaken = 0; + CPLCond *hCond = nullptr; + + CPLErrorAccumulator *poErrorAccumulator = nullptr; +}; static void ChunkThreadMain(void *pThreadData) @@ -1086,6 +1089,10 @@ static void ChunkThreadMain(void *pThreadData) CPLReleaseMutex(psData->hCondMutex); } + auto oAccumulator = + psData->poErrorAccumulator->InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + psData->eErr = psData->poOperation->WarpRegion( pasChunkInfo->dx, pasChunkInfo->dy, pasChunkInfo->dsx, pasChunkInfo->dsy, pasChunkInfo->sx, pasChunkInfo->sy, @@ -1150,13 +1157,13 @@ CPLErr GDALWarpOperation::ChunkAndWarpMulti(int nDstXOff, int nDstYOff, /* information for each region. */ /* -------------------------------------------------------------------- */ ChunkThreadData volatile asThreadData[2] = {}; - memset(reinterpret_cast( - const_cast(&asThreadData)), - 0, sizeof(asThreadData)); - asThreadData[0].poOperation = this; - asThreadData[0].hIOMutex = hIOMutex; - asThreadData[1].poOperation = this; - asThreadData[1].hIOMutex = hIOMutex; + CPLErrorAccumulator oErrorAccumulator; + for (int i = 0; i < 2; ++i) + { + asThreadData[i].poOperation = this; + asThreadData[i].hIOMutex = hIOMutex; + asThreadData[i].poErrorAccumulator = &oErrorAccumulator; + } double dfPixelsProcessed = 0.0; double dfTotalPixels = static_cast(nDstXSize) * nDstYSize; @@ -1260,6 +1267,8 @@ CPLErr GDALWarpOperation::ChunkAndWarpMulti(int nDstXOff, int nDstYOff, WipeChunkList(); + oErrorAccumulator.ReplayErrors(); + psOptions->pfnProgress(1.0, "", psOptions->pProgressArg); return eErr; diff --git a/autotest/alg/warp.py b/autotest/alg/warp.py index 22054d7abdd7..5f011f9b395c 100755 --- a/autotest/alg/warp.py +++ b/autotest/alg/warp.py @@ -1964,3 +1964,30 @@ def test_warp_nodata_substitution(dt, expected_val, resampling): struct.unpack("d", out_ds.ReadRaster(0, 0, 1, 1, buf_type=gdal.GDT_Float64))[0] == expected_val ) + + +############################################################################### +# Test propagation of errors from I/O threads to main thread in multi-threaded reading + + +@gdaltest.enable_exceptions() +def test_warp_multi_threaded_errors(tmp_vsimem): + + filename1 = str(tmp_vsimem / "tmp1.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename1, 1, 1) + ds.SetGeoTransform([2, 1, 0, 49, 0, -1]) + ds.Close() + + filename2 = str(tmp_vsimem / "tmp2.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename2, 1, 1) + ds.SetGeoTransform([3, 1, 0, 49, 0, -1]) + ds.Close() + + vrt_filename = str(tmp_vsimem / "tmp.vrt") + gdal.BuildVRT(vrt_filename, [filename1, filename2]) + + gdal.Unlink(filename2) + + with gdal.Open(vrt_filename) as ds: + with pytest.raises(Exception): + gdal.Warp("", ds, format="MEM", multithread=True) From 630ab1bd206765ed84c8ee3bba0665296f0f44b5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:25:39 +0100 Subject: [PATCH 04/10] gdalwarp: use CPLErrorAccumulator --- apps/gdalwarp_bin.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/apps/gdalwarp_bin.cpp b/apps/gdalwarp_bin.cpp index 29c168a352c2..80618fb1a997 100644 --- a/apps/gdalwarp_bin.cpp +++ b/apps/gdalwarp_bin.cpp @@ -204,20 +204,17 @@ MAIN_START(argc, argv) } else { - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); - hDstDS = GDALOpenEx( - sOptionsForBinary.osDstFilename.c_str(), - GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_UPDATE, nullptr, - sOptionsForBinary.aosDestOpenOptions.List(), nullptr); - CPLUninstallErrorHandlerAccumulator(); + CPLErrorAccumulator oErrorAccumulator; + { + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + hDstDS = GDALOpenEx( + sOptionsForBinary.osDstFilename.c_str(), + GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_UPDATE, + nullptr, sOptionsForBinary.aosDestOpenOptions.List(), nullptr); + } if (hDstDS != nullptr) { - for (size_t i = 0; i < aoErrors.size(); i++) - { - CPLError(aoErrors[i].type, aoErrors[i].no, "%s", - aoErrors[i].msg.c_str()); - } + oErrorAccumulator.ReplayErrors(); } } From 757e5ab36eecb04ad36c1699109e90e50a4efee1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:25:47 +0100 Subject: [PATCH 05/10] GTI: use CPLErrorAccumulator --- frmts/gti/gdaltileindexdataset.cpp | 32 +++++++----------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/frmts/gti/gdaltileindexdataset.cpp b/frmts/gti/gdaltileindexdataset.cpp index 79a9ca3ce978..288b86337438 100644 --- a/frmts/gti/gdaltileindexdataset.cpp +++ b/frmts/gti/gdaltileindexdataset.cpp @@ -374,11 +374,11 @@ class GDALTileIndexDataset final : public GDALPamDataset { std::atomic *pnCompletedJobs = nullptr; std::atomic *pbSuccess = nullptr; + CPLErrorAccumulator *poErrorAccumulator = nullptr; GDALTileIndexDataset *poDS = nullptr; GDALTileIndexDataset::QueueWorkingStates *poQueueWorkingStates = nullptr; int nBandNrMax = 0; - std::string *posErrorMsg = nullptr; int nXOff = 0; int nYOff = 0; @@ -4624,6 +4624,7 @@ CPLErr GDALTileIndexDataset::IRasterIO( if (m_bLastMustUseMultiThreading) { + CPLErrorAccumulator oErrorAccumulator; std::atomic bSuccess = true; const int nContributingSources = static_cast(m_aoSourceDesc.size()); @@ -4654,16 +4655,15 @@ CPLErr GDALTileIndexDataset::IRasterIO( auto oQueue = psThreadPool->CreateJobQueue(); std::atomic nCompletedJobs = 0; - std::string osErrorMsg; for (auto &oSourceDesc : m_aoSourceDesc) { auto psJob = new RasterIOJob(); psJob->poDS = this; psJob->pbSuccess = &bSuccess; + psJob->poErrorAccumulator = &oErrorAccumulator; psJob->pnCompletedJobs = &nCompletedJobs; psJob->poQueueWorkingStates = &m_oQueueWorkingStates; psJob->nBandNrMax = nBandNrMax; - psJob->posErrorMsg = &osErrorMsg; psJob->nXOff = nXOff; psJob->nYOff = nYOff; psJob->nXSize = nXSize; @@ -4702,10 +4702,7 @@ CPLErr GDALTileIndexDataset::IRasterIO( } } - if (!osErrorMsg.empty()) - { - CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str()); - } + oErrorAccumulator.ReplayErrors(); if (bSuccess && psExtraArg->pfnProgress) { @@ -4754,22 +4751,16 @@ void GDALTileIndexDataset::RasterIOJob::Func(void *pData) SourceDesc oSourceDesc; - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); + auto oAccumulator = psJob->poErrorAccumulator->InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + const bool bCanOpenSource = psJob->poDS->GetSourceDesc(osTileName, oSourceDesc, &psJob->poQueueWorkingStates->oMutex) && oSourceDesc.poDS; - CPLUninstallErrorHandlerAccumulator(); if (!bCanOpenSource) { - if (!aoErrors.empty()) - { - std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex); - if (psJob->posErrorMsg->empty()) - *(psJob->posErrorMsg) = aoErrors.back().msg; - } *psJob->pbSuccess = false; } else @@ -4799,8 +4790,6 @@ void GDALTileIndexDataset::RasterIOJob::Func(void *pData) dfYSize = psJob->psExtraArg->dfYSize; } - aoErrors.clear(); - CPLInstallErrorHandlerAccumulator(aoErrors); const bool bRenderOK = psJob->poDS->RenderSource( oSourceDesc, /*bNeedInitBuffer = */ true, psJob->nBandNrMax, @@ -4810,16 +4799,9 @@ void GDALTileIndexDataset::RasterIOJob::Func(void *pData) psJob->nBandCount, psJob->panBandMap, psJob->nPixelSpace, psJob->nLineSpace, psJob->nBandSpace, &sArg, *(poWorkingState.get())) == CE_None; - CPLUninstallErrorHandlerAccumulator(); if (!bRenderOK) { - if (!aoErrors.empty()) - { - std::lock_guard oLock(psJob->poQueueWorkingStates->oMutex); - if (psJob->posErrorMsg->empty()) - *(psJob->posErrorMsg) = aoErrors.back().msg; - } *psJob->pbSuccess = false; } From f308367b6d49889081af2d3bf081955baad3a2b4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:25:58 +0100 Subject: [PATCH 06/10] GTiff: use CPLErrorAccumulator --- frmts/gtiff/gtiffdataset_read.cpp | 74 +++++++++++----------------- frmts/gtiff/gtiffsplitbitmapband.cpp | 17 ++++--- 2 files changed, 38 insertions(+), 53 deletions(-) diff --git a/frmts/gtiff/gtiffdataset_read.cpp b/frmts/gtiff/gtiffdataset_read.cpp index 13c4a30f8e57..da8898bd5c76 100644 --- a/frmts/gtiff/gtiffdataset_read.cpp +++ b/frmts/gtiff/gtiffdataset_read.cpp @@ -343,8 +343,7 @@ struct GTiffDecompressContext // already acquired. std::recursive_mutex oMutex{}; bool bSuccess = true; - - std::vector aoErrors{}; + CPLErrorAccumulator oErrorAccumulator{}; VSIVirtualHandle *poHandle = nullptr; GTiffDataset *poDS = nullptr; @@ -399,19 +398,6 @@ struct GTiffDecompressJob vsi_l_offset nSize = 0; }; -/************************************************************************/ -/* ThreadDecompressionFuncErrorHandler() */ -/************************************************************************/ - -static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( - CPLErr eErr, CPLErrorNum eErrorNum, const char *pszMsg) -{ - GTiffDecompressContext *psContext = - static_cast(CPLGetErrorHandlerUserData()); - std::lock_guard oLock(psContext->oMutex); - psContext->aoErrors.emplace_back(eErr, eErrorNum, pszMsg); -} - /************************************************************************/ /* ThreadDecompressionFunc() */ /************************************************************************/ @@ -422,8 +408,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( auto psContext = psJob->psContext; auto poDS = psContext->poDS; - CPLErrorHandlerPusher oErrorHandler(ThreadDecompressionFuncErrorHandler, - psContext); + auto oAccumulator = psContext->oErrorAccumulator.InstallForCurrentScope(); const int nBandsPerStrile = poDS->m_nPlanarConfig == PLANARCONFIG_CONTIG ? poDS->nBands : 1; @@ -1482,11 +1467,7 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize, // Undo effect of above TemporarilyDropReadWriteLock() ReacquireReadWriteLock(); - // Re-emit errors caught in threads - for (const auto &oError : sContext.aoErrors) - { - CPLError(oError.type, oError.no, "%s", oError.msg.c_str()); - } + sContext.oErrorAccumulator.ReplayErrors(); } return sContext.bSuccess ? CE_None : CE_Failure; @@ -3830,31 +3811,33 @@ GDALDataset *GTiffDataset::Open(GDALOpenInfo *poOpenInfo) } // Store errors/warnings and emit them later. - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); - CPLSetCurrentErrorHandlerCatchDebug(FALSE); - const bool bDeferStrileLoading = CPLTestBool( - CPLGetConfigOption("GTIFF_USE_DEFER_STRILE_LOADING", "YES")); - TIFF *l_hTIFF = VSI_TIFFOpen( - pszFilename, - poOpenInfo->eAccess == GA_ReadOnly - ? ((bStreaming || !bDeferStrileLoading) ? "rC" : "rDOC") - : (!bDeferStrileLoading ? "r+C" : "r+DC"), - poOpenInfo->fpL); - CPLUninstallErrorHandlerAccumulator(); + TIFF *l_hTIFF; + CPLErrorAccumulator oErrorAccumulator; + { + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + CPLSetCurrentErrorHandlerCatchDebug(FALSE); + const bool bDeferStrileLoading = CPLTestBool( + CPLGetConfigOption("GTIFF_USE_DEFER_STRILE_LOADING", "YES")); + l_hTIFF = VSI_TIFFOpen( + pszFilename, + poOpenInfo->eAccess == GA_ReadOnly + ? ((bStreaming || !bDeferStrileLoading) ? "rC" : "rDOC") + : (!bDeferStrileLoading ? "r+C" : "r+DC"), + poOpenInfo->fpL); + }; // Now emit errors and change their criticality if needed // We only emit failures if we didn't manage to open the file. // Otherwise it makes Python bindings unhappy (#5616). - for (size_t iError = 0; iError < aoErrors.size(); ++iError) + for (const auto &oError : oErrorAccumulator.GetErrors()) { ReportError(pszFilename, - (l_hTIFF == nullptr && aoErrors[iError].type == CE_Failure) + (l_hTIFF == nullptr && oError.type == CE_Failure) ? CE_Failure : CE_Warning, - aoErrors[iError].no, "%s", aoErrors[iError].msg.c_str()); + oError.no, "%s", oError.msg.c_str()); } - aoErrors.resize(0); if (l_hTIFF == nullptr) return nullptr; @@ -4136,16 +4119,18 @@ void GTiffDataset::LookForProjectionFromGeoTIFF() bool bHasErrorBefore = CPLGetLastErrorType() != 0; // Collect (PROJ) error messages and remit them later as warnings - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); - const int ret = GTIFGetDefn(hGTIF, psGTIFDefn); - CPLUninstallErrorHandlerAccumulator(); + int ret; + CPLErrorAccumulator oErrorAccumulator; + { + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + ret = GTIFGetDefn(hGTIF, psGTIFDefn); + } bool bWarnAboutEllipsoid = true; if (ret) { - CPLInstallErrorHandlerAccumulator(aoErrors); + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); if (psGTIFDefn->Ellipsoid == 4326 && psGTIFDefn->SemiMajor == 6378137 && @@ -4158,7 +4143,6 @@ void GTiffDataset::LookForProjectionFromGeoTIFF() } OGRSpatialReferenceH hSRS = GTIFGetOGISDefnAsOSR(hGTIF, psGTIFDefn); - CPLUninstallErrorHandlerAccumulator(); if (hSRS) { @@ -4171,7 +4155,7 @@ void GTiffDataset::LookForProjectionFromGeoTIFF() } std::set oSetErrorMsg; - for (const auto &oError : aoErrors) + for (const auto &oError : oErrorAccumulator.GetErrors()) { if (!bWarnAboutEllipsoid && oError.msg.find("ellipsoid not found") != std::string::npos) diff --git a/frmts/gtiff/gtiffsplitbitmapband.cpp b/frmts/gtiff/gtiffsplitbitmapband.cpp index 0deafe5ebd48..4dd6030de967 100644 --- a/frmts/gtiff/gtiffsplitbitmapband.cpp +++ b/frmts/gtiff/gtiffsplitbitmapband.cpp @@ -84,21 +84,22 @@ CPLErr GTiffSplitBitmapBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff, { ++m_poGDS->m_nLoadedBlock; - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); - int nRet = TIFFReadScanline(m_poGDS->m_hTIFF, m_poGDS->m_pabyBlockBuf, + CPLErrorAccumulator oErrorAccumulator; + int nRet; + { + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + nRet = TIFFReadScanline(m_poGDS->m_hTIFF, m_poGDS->m_pabyBlockBuf, m_poGDS->m_nLoadedBlock, 0); - CPLUninstallErrorHandlerAccumulator(); + } - for (size_t iError = 0; iError < aoErrors.size(); ++iError) + for (const auto &oError : oErrorAccumulator.GetErrors()) { - ReportError(aoErrors[iError].type, aoErrors[iError].no, "%s", - aoErrors[iError].msg.c_str()); + ReportError(oError.type, oError.no, "%s", oError.msg.c_str()); // FAX decoding only handles EOF condition as a warning, so // catch it so as to turn on error when attempting to read // following lines, to avoid performance issues. if (!m_poGDS->m_bIgnoreReadErrors && - aoErrors[iError].msg.find("Premature EOF") != std::string::npos) + oError.msg.find("Premature EOF") != std::string::npos) { m_nLastLineValid = nBlockYOff; nRet = -1; From 23b33b8d493a91643fa9aa57092b7b8e089a6e72 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:26:07 +0100 Subject: [PATCH 07/10] gdalmultidim: use CPLErrorAccumulator --- gcore/gdalmultidim.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gcore/gdalmultidim.cpp b/gcore/gdalmultidim.cpp index 18437311716b..456e90f717e4 100644 --- a/gcore/gdalmultidim.cpp +++ b/gcore/gdalmultidim.cpp @@ -14278,11 +14278,14 @@ void GDALPamMultiDim::Save() } } - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); - const int bSaved = - CPLSerializeXMLTreeToFile(oTree.get(), d->m_osPamFilename.c_str()); - CPLUninstallErrorHandlerAccumulator(); + int bSaved; + CPLErrorAccumulator oErrorAccumulator; + { + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + bSaved = + CPLSerializeXMLTreeToFile(oTree.get(), d->m_osPamFilename.c_str()); + } const char *pszNewPam = nullptr; if (!bSaved && PamGetProxy(d->m_osFilename.c_str()) == nullptr && @@ -14293,10 +14296,7 @@ void GDALPamMultiDim::Save() } else { - for (const auto &oError : aoErrors) - { - CPLError(oError.type, oError.no, "%s", oError.msg.c_str()); - } + oErrorAccumulator.ReplayErrors(); } } From 4757467079331a6769e1fc18e6b3c8cd9863dd54 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:26:20 +0100 Subject: [PATCH 08/10] ogrspatialreference.cpp: use CPLErrorAccumulator --- ogr/ogrspatialreference.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ogr/ogrspatialreference.cpp b/ogr/ogrspatialreference.cpp index 5d4477a40573..f759b9109b9d 100644 --- a/ogr/ogrspatialreference.cpp +++ b/ogr/ogrspatialreference.cpp @@ -1754,12 +1754,15 @@ OGRErr OGRSpatialReference::exportToWkt(char **ppszResult, d->getPROJContext(), d->m_pj_crs, true, true); } - std::vector aoErrors; - CPLInstallErrorHandlerAccumulator(aoErrors); - const char *pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, - wktFormat, aosOptions.List()); - CPLUninstallErrorHandlerAccumulator(); - for (const auto &oError : aoErrors) + CPLErrorAccumulator oErrorAccumulator; + const char *pszWKT; + { + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); + pszWKT = proj_as_wkt(ctxt, boundCRS ? boundCRS : d->m_pj_crs, wktFormat, + aosOptions.List()); + } + for (const auto &oError : oErrorAccumulator.GetErrors()) { if (pszFormat[0] == '\0' && (oError.msg.find("Unsupported conversion method") != From 1d78dbc3b44bac3dd030f4c64156fa86a6b121cd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:26:38 +0100 Subject: [PATCH 09/10] httpdriver: use CPLErrorAccumulator --- frmts/http/httpdriver.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/frmts/http/httpdriver.cpp b/frmts/http/httpdriver.cpp index 333de6686ab0..2611bba491c8 100644 --- a/frmts/http/httpdriver.cpp +++ b/frmts/http/httpdriver.cpp @@ -136,24 +136,21 @@ static GDALDataset *HTTPOpen(GDALOpenInfo *poOpenInfo) /* suppress errors as not all drivers support /vsimem */ GDALDataset *poDS; - std::vector aoErrors; + CPLErrorAccumulator oErrorAccumulator; { CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); - CPLInstallErrorHandlerAccumulator(aoErrors); + auto oAccumulator = oErrorAccumulator.InstallForCurrentScope(); + CPL_IGNORE_RET_VAL(oAccumulator); poDS = GDALDataset::Open(osResultFilename, poOpenInfo->nOpenFlags & ~GDAL_OF_SHARED, poOpenInfo->papszAllowedDrivers, poOpenInfo->papszOpenOptions, nullptr); - CPLUninstallErrorHandlerAccumulator(); } // Re-emit silenced errors if open was successful if (poDS) { - for (const auto &oError : aoErrors) - { - CPLError(oError.type, oError.no, "%s", oError.msg.c_str()); - } + oErrorAccumulator.ReplayErrors(); } // The JP2OpenJPEG driver may need to reopen the file, hence this special From 4970a4f75524ff6bd3a360dba6de0cc0d671aaa4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Mar 2025 15:26:53 +0100 Subject: [PATCH 10/10] Remove no longer used CPLInstallErrorHandlerAccumulator() / CPLUninstallErrorHandlerAccumulator() --- port/cpl_error.cpp | 24 ------------------------ port/cpl_error_internal.h | 4 ---- 2 files changed, 28 deletions(-) diff --git a/port/cpl_error.cpp b/port/cpl_error.cpp index 522cb9c97158..37208d8f0d43 100644 --- a/port/cpl_error.cpp +++ b/port/cpl_error.cpp @@ -1535,30 +1535,6 @@ bool CPLIsDefaultErrorHandlerAndCatchDebug() gbCatchDebug && pfnErrorHandler == CPLDefaultErrorHandler; } -/************************************************************************/ -/* CPLErrorAccumulatorFunc() */ -/************************************************************************/ - -static void CPL_STDCALL CPLErrorAccumulatorFunc(CPLErr eErr, CPLErrorNum no, - const char *msg) -{ - std::vector *paoErrors = - static_cast *>( - CPLGetErrorHandlerUserData()); - paoErrors->push_back(CPLErrorHandlerAccumulatorStruct(eErr, no, msg)); -} - -void CPLInstallErrorHandlerAccumulator( - std::vector &aoErrors) -{ - CPLPushErrorHandlerEx(CPLErrorAccumulatorFunc, &aoErrors); -} - -void CPLUninstallErrorHandlerAccumulator() -{ - CPLPopErrorHandler(); -} - /************************************************************************/ /* CPLErrorStateBackuper::CPLErrorStateBackuper() */ /************************************************************************/ diff --git a/port/cpl_error_internal.h b/port/cpl_error_internal.h index 3fcff32952b3..bb44e7ab360b 100644 --- a/port/cpl_error_internal.h +++ b/port/cpl_error_internal.h @@ -45,10 +45,6 @@ class CPL_DLL CPLErrorHandlerAccumulatorStruct } }; -void CPL_DLL CPLInstallErrorHandlerAccumulator( - std::vector &aoErrors); -void CPL_DLL CPLUninstallErrorHandlerAccumulator(); - /************************************************************************/ /* CPLErrorAccumulator */ /************************************************************************/