Skip to content

Commit 599ac5a

Browse files
Parsing logic: fail gracefully on unexpected input (#1237)
* Add ReadError error type * Move error throwing outside of ADIOS1 library Some weird compiler configurations don't understand error type symbols across libraries, so keep the symbols entirely to the main library. * Backend additions: ADIOS2 * Backend additions: ADIOS1 * Backend additions: HDF5 * Backend additions: JSON * Fully clear task queue upon failure * Error handling at Series level This is the commit with the most complicated logic changes. It implements skipping broken iterations at the Series level. This includes the Streaming API (Series::readIterations()), as well as our three iteration encodings. On the positive side: The most complex logic changes have already been prepared in the topic-adios2-append PR * Error handling throughout the openPMD object model Nothing too complicated, just tedious * Testing, and adapt tests to new Error types * Replace no_such_file_error by ReadError `using no_such_file_error = error::ReadError` for backwards compatibility in user code. * Forward errors instead of creating new ones @todo: Check if this needs to be done elsewhere * Test opening single broken filebased iterations * Use std::stable_sort in ADIOS2 destructor * Add removed .cpp files back to ADIOS1 backend implementation * Code review * Add export attribute to functions in ThrowError.hpp
1 parent 990ac95 commit 599ac5a

34 files changed

+1710
-433
lines changed

CMakeLists.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,14 +540,12 @@ set(IO_SOURCE
540540
src/IO/ADIOS/ADIOS2PreloadAttributes.cpp
541541
src/IO/InvalidatableFile.cpp)
542542
set(IO_ADIOS1_SEQUENTIAL_SOURCE
543-
src/Error.cpp
544543
src/auxiliary/Filesystem.cpp
545544
src/ChunkInfo.cpp
546545
src/IO/ADIOS/CommonADIOS1IOHandler.cpp
547546
src/IO/ADIOS/ADIOS1IOHandler.cpp
548547
src/IO/IOTask.cpp)
549548
set(IO_ADIOS1_SOURCE
550-
src/Error.cpp
551549
src/auxiliary/Filesystem.cpp
552550
src/ChunkInfo.cpp
553551
src/IO/ADIOS/CommonADIOS1IOHandler.cpp
@@ -674,6 +672,9 @@ if(openPMD_HAVE_ADIOS1)
674672
target_compile_definitions(openPMD.ADIOS1.Serial PRIVATE openPMD_HAVE_ADIOS1=1)
675673
target_compile_definitions(openPMD.ADIOS1.Serial PRIVATE openPMD_HAVE_MPI=0)
676674
target_compile_definitions(openPMD.ADIOS1.Serial PRIVATE _NOMPI) # ADIOS header
675+
# This ensures that the ADIOS1 targets don't ever include Error.hpp
676+
# To avoid incompatible error types in weird compile configurations
677+
target_compile_definitions(openPMD.ADIOS1.Serial PRIVATE OPENPMD_ADIOS1_IMPLEMENTATION)
677678

678679
if(openPMD_HAVE_MPI)
679680
set_target_properties(openPMD.ADIOS1.Parallel PROPERTIES
@@ -711,6 +712,9 @@ if(openPMD_HAVE_ADIOS1)
711712
target_compile_definitions(openPMD.ADIOS1.Parallel PRIVATE openPMD_HAVE_MPI=0)
712713
target_compile_definitions(openPMD.ADIOS1.Parallel PRIVATE _NOMPI) # ADIOS header
713714
endif()
715+
# This ensures that the ADIOS1 targets don't ever include Error.hpp
716+
# To avoid incompatible error types in weird compile configurations
717+
target_compile_definitions(openPMD.ADIOS1.Parallel PRIVATE OPENPMD_ADIOS1_IMPLEMENTATION)
714718

715719
# Runtime parameter and API status checks ("asserts")
716720
if(openPMD_USE_VERIFY)
@@ -909,6 +913,7 @@ if(openPMD_USE_INVASIVE_TESTS)
909913
message(WARNING "Invasive tests that redefine class signatures are "
910914
"known to fail on Windows!")
911915
endif()
916+
target_compile_definitions(openPMD PRIVATE openPMD_USE_INVASIVE_TESTS=1)
912917
endif()
913918

914919
if(openPMD_BUILD_TESTING)

include/openPMD/Error.hpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
#pragma once
22

3+
#include "openPMD/ThrowError.hpp"
4+
35
#include <exception>
6+
#include <optional>
47
#include <string>
58
#include <utility>
69
#include <vector>
710

11+
#if defined(OPENPMD_ADIOS1_IMPLEMENTATION)
12+
static_assert(false, "ADIOS1 implementation must not include Error.hpp");
13+
#endif
14+
815
namespace openPMD
916
{
1017
/**
@@ -80,5 +87,39 @@ namespace error
8087
public:
8188
Internal(std::string const &what);
8289
};
90+
91+
/*
92+
* Read error concerning a specific object.
93+
*/
94+
class ReadError : public Error
95+
{
96+
public:
97+
AffectedObject affectedObject;
98+
Reason reason;
99+
// If empty, then the error is thrown by the frontend
100+
std::optional<std::string> backend;
101+
std::string description; // object path, further details, ...
102+
103+
ReadError(
104+
AffectedObject,
105+
Reason,
106+
std::optional<std::string> backend_in,
107+
std::string description_in);
108+
};
109+
110+
/*
111+
* Inrecoverable parse error from the frontend.
112+
*/
113+
class ParseError : public Error
114+
{
115+
public:
116+
ParseError(std::string what);
117+
};
83118
} // namespace error
119+
120+
/**
121+
* @brief Backward-compatibility alias for no_such_file_error.
122+
*
123+
*/
124+
using no_such_file_error = error::ReadError;
84125
} // namespace openPMD

include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,9 @@ namespace detail
10701070
template <typename BA>
10711071
void enqueue(BA &&ba, decltype(m_buffer) &);
10721072

1073+
template <typename... Args>
1074+
void flush(Args &&...args);
1075+
10731076
struct ADIOS2FlushParams
10741077
{
10751078
/*
@@ -1103,7 +1106,7 @@ namespace detail
11031106
* deferred IO tasks had been queued.
11041107
*/
11051108
template <typename F>
1106-
void flush(
1109+
void flush_impl(
11071110
ADIOS2FlushParams flushParams,
11081111
F &&performPutsGets,
11091112
bool writeAttributes,
@@ -1114,7 +1117,7 @@ namespace detail
11141117
* and does not flush unconditionally.
11151118
*
11161119
*/
1117-
void flush(ADIOS2FlushParams, bool writeAttributes = false);
1120+
void flush_impl(ADIOS2FlushParams, bool writeAttributes = false);
11181121

11191122
/**
11201123
* @brief Begin or end an ADIOS step.

include/openPMD/IO/AbstractIOHandler.hpp

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,10 @@
3434
#include <queue>
3535
#include <stdexcept>
3636
#include <string>
37+
#include <type_traits>
3738

3839
namespace openPMD
3940
{
40-
class no_such_file_error : public std::runtime_error
41-
{
42-
public:
43-
no_such_file_error(std::string const &what_arg)
44-
: std::runtime_error(what_arg)
45-
{}
46-
virtual ~no_such_file_error()
47-
{}
48-
};
4941

5042
class unsupported_data_error : public std::runtime_error
5143
{
@@ -143,6 +135,48 @@ namespace internal
143135
///< Special state only active while internal routines are
144136
///< running.
145137
};
138+
139+
// @todo put this somewhere else
140+
template <typename Functor, typename... Args>
141+
auto withRWAccess(SeriesStatus &status, Functor &&functor, Args &&...args)
142+
-> decltype(std::forward<Functor>(functor)(std::forward<Args>(args)...))
143+
{
144+
using Res = decltype(std::forward<Functor>(functor)(
145+
std::forward<Args>(args)...));
146+
if constexpr (std::is_void_v<Res>)
147+
{
148+
auto oldStatus = status;
149+
status = internal::SeriesStatus::Parsing;
150+
try
151+
{
152+
std::forward<decltype(functor)>(functor)();
153+
}
154+
catch (...)
155+
{
156+
status = oldStatus;
157+
throw;
158+
}
159+
status = oldStatus;
160+
return;
161+
}
162+
else
163+
{
164+
auto oldStatus = status;
165+
status = internal::SeriesStatus::Parsing;
166+
Res res;
167+
try
168+
{
169+
res = std::forward<decltype(functor)>(functor)();
170+
}
171+
catch (...)
172+
{
173+
status = oldStatus;
174+
throw;
175+
}
176+
status = oldStatus;
177+
return res;
178+
}
179+
}
146180
} // namespace internal
147181

148182
/** Interface for communicating between logical and physically persistent data.

include/openPMD/IO/AbstractIOHandlerImpl.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,13 @@ class AbstractIOHandlerImpl
208208
{
209209
std::cerr << "[AbstractIOHandlerImpl] IO Task "
210210
<< internal::operationAsString(i.operation)
211-
<< " failed with exception. Removing task"
212-
<< " from IO queue and passing on the exception."
211+
<< " failed with exception. Clearing IO queue and "
212+
"passing on the exception."
213213
<< std::endl;
214-
(*m_handler).m_work.pop();
214+
while (!m_handler->m_work.empty())
215+
{
216+
m_handler->m_work.pop();
217+
}
215218
throw;
216219
}
217220
(*m_handler).m_work.pop();

include/openPMD/Iteration.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ class Iteration : public Attributable
286286
std::string filePath, std::string const &groupPath, bool beginStep);
287287
void readGorVBased(std::string const &groupPath, bool beginStep);
288288
void read_impl(std::string const &groupPath);
289+
void readMeshes(std::string const &meshesPath);
290+
void readParticles(std::string const &particlesPath);
289291

290292
/**
291293
* Status after beginning an IO step. Currently includes:

include/openPMD/ReadIterations.hpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,21 @@ class SeriesIterator
104104

105105
std::optional<SeriesIterator *> nextIterationInStep();
106106

107-
std::optional<SeriesIterator *> nextStep();
107+
/*
108+
* When a step cannot successfully be opened, the method nextStep() calls
109+
* itself again recursively.
110+
* (Recursion massively simplifies the logic here, and it only happens
111+
* in case of error.)
112+
* After successfully beginning a step, this methods needs to remember, how
113+
* many broken steps have been skipped. In case the Series does not use
114+
* the /data/snapshot attribute, this helps figuring out which iteration
115+
* is now active. Hence, recursion_depth.
116+
*/
117+
std::optional<SeriesIterator *> nextStep(size_t recursion_depth);
108118

109119
std::optional<SeriesIterator *> loopBody();
120+
121+
void deactivateDeadIteration(iteration_index_t);
110122
};
111123

112124
/**

include/openPMD/Series.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,9 +611,15 @@ OPENPMD_private
611611
* Iterations/Records/Record Components etc.
612612
* If series.iterations contains the attribute `snapshot`, returns its
613613
* value.
614+
* If do_always_throw_errors is false, this method will try to handle errors
615+
* and turn them into a warning (useful when parsing a Series, since parsing
616+
* should succeed without issue).
617+
* If true, the error will always be re-thrown (useful when using
618+
* ReadIterations since those methods should be aware when the current step
619+
* is broken).
614620
*/
615621
std::optional<std::deque<IterationIndex_t> >
616-
readGorVBased(bool init = true);
622+
readGorVBased(bool do_always_throw_errors, bool init);
617623
void readBase();
618624
std::string iterationFilename(IterationIndex_t i);
619625

include/openPMD/ThrowError.hpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* Copyright 2022 Franz Poeschel
2+
*
3+
* This file is part of openPMD-api.
4+
*
5+
* openPMD-api is free software: you can redistribute it and/or modify
6+
* it under the terms of of either the GNU General Public License or
7+
* the GNU Lesser General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* openPMD-api is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License and the GNU Lesser General Public License
15+
* for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* and the GNU Lesser General Public License along with openPMD-api.
19+
* If not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
22+
/*
23+
* For objects that must not include Error.hpp but still need to throw errors.
24+
* In some exotic compiler configurations (clang-6 with libc++),
25+
* including Error.hpp into the ADIOS1 backend leads to incompatible error type
26+
* symbols.
27+
* So, use only the functions defined in here in the ADIOS1 backend.
28+
* Definitions are in Error.cpp.
29+
*/
30+
31+
#pragma once
32+
33+
#include "openPMD/auxiliary/Export.hpp"
34+
35+
#include <optional>
36+
#include <string>
37+
#include <vector>
38+
39+
namespace openPMD::error
40+
{
41+
enum class AffectedObject
42+
{
43+
Attribute,
44+
Dataset,
45+
File,
46+
Group,
47+
Other
48+
};
49+
50+
enum class Reason
51+
{
52+
NotFound,
53+
CannotRead,
54+
UnexpectedContent,
55+
Inaccessible,
56+
Other
57+
};
58+
59+
[[noreturn]] OPENPMDAPI_EXPORT void
60+
throwBackendConfigSchema(std::vector<std::string> jsonPath, std::string what);
61+
62+
[[noreturn]] OPENPMDAPI_EXPORT void
63+
throwOperationUnsupportedInBackend(std::string backend, std::string what);
64+
65+
[[noreturn]] OPENPMDAPI_EXPORT void throwReadError(
66+
AffectedObject affectedObject,
67+
Reason reason_in,
68+
std::optional<std::string> backend,
69+
std::string description_in);
70+
} // namespace openPMD::error

include/openPMD/auxiliary/JSON_internal.hpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
#include "openPMD/config.hpp"
2525

26-
#include "openPMD/Error.hpp"
27-
2826
#include <nlohmann/json.hpp>
2927
#include <toml.hpp>
3028

0 commit comments

Comments
 (0)