Skip to content

Commit 29e8ea0

Browse files
bkietzpitrou
andauthored
GH-44491: [C++] StatusConstant- cheaply copied const Status (#44493)
### Rationale for this change It'd be convenient to construct placeholder error Status cheaply. ### What changes are included in this PR? A constant error Status can now be constructed wrapped in a function like ```c++ Status UninitializedResult() { static StatusConstant uninitialized_result{StatusCode::UnknownError, "Uninitialized Result<T>"}; return uninitialized_result; } ``` Copying the constant status is relatively cheap (no heap interaction), so it's suitable for use as a placeholder error status. Added `bool Status::State::is_constant` which causes copies to be shallow and skips destruction. Also `Status::state_` is a const pointer now; this helps to ensure that it is obvious when we mutate state_ (as in AddContextLine). ### Are these changes tested? Minimal unit test added. The main consideration is probably benchmarks to make sure hot paths don't get much slower. ### Are there any user-facing changes? This API is not currently public; no user-facing changes. * GitHub Issue: #44491 Lead-authored-by: Benjamin Kietzman <[email protected]> Co-authored-by: Antoine Pitrou <[email protected]> Signed-off-by: Antoine Pitrou <[email protected]>
1 parent 474c675 commit 29e8ea0

File tree

7 files changed

+114
-31
lines changed

7 files changed

+114
-31
lines changed

cpp/src/arrow/buffer_test.cc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -511,13 +511,8 @@ TEST(TestBuffer, CopySliceEmpty) {
511511
}
512512

513513
TEST(TestBuffer, ToHexString) {
514-
const uint8_t data_array[] = "\a0hex string\xa9";
515-
std::basic_string<uint8_t> data_str = data_array;
516-
517-
auto data = reinterpret_cast<const uint8_t*>(data_str.c_str());
518-
519-
Buffer buf(data, data_str.size());
520-
514+
const std::string data_str = "\a0hex string\xa9";
515+
Buffer buf(data_str);
521516
ASSERT_EQ(buf.ToHexString(), std::string("073068657820737472696E67A9"));
522517
}
523518

cpp/src/arrow/result.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919

2020
#include <string>
2121

22+
#include "arrow/status_internal.h"
2223
#include "arrow/util/logging.h"
2324

24-
namespace arrow {
25-
26-
namespace internal {
25+
namespace arrow::internal {
2726

2827
void DieWithMessage(const std::string& msg) { ARROW_LOG(FATAL) << msg; }
2928

3029
void InvalidValueOrDie(const Status& st) {
3130
DieWithMessage(std::string("ValueOrDie called on an error: ") + st.ToString());
3231
}
3332

34-
} // namespace internal
33+
Status UninitializedResult() {
34+
static StatusConstant uninitialized_result{StatusCode::UnknownError,
35+
"Uninitialized Result<T>"};
36+
return uninitialized_result;
37+
}
3538

36-
} // namespace arrow
39+
} // namespace arrow::internal

cpp/src/arrow/result.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ ARROW_EXPORT void DieWithMessage(const std::string& msg);
3939

4040
ARROW_EXPORT void InvalidValueOrDie(const Status& st);
4141

42+
ARROW_EXPORT Status UninitializedResult();
43+
4244
} // namespace internal
4345

4446
/// A class for representing either a usable value, or an error.
@@ -112,7 +114,7 @@ class [[nodiscard]] Result : public util::EqualityComparable<Result<T>> {
112114
/// an empty vector, it will actually invoke the default constructor of
113115
/// Result.
114116
explicit Result() noexcept // NOLINT(runtime/explicit)
115-
: status_(Status::UnknownError("Uninitialized Result<T>")) {}
117+
: status_(internal::UninitializedResult()) {}
116118

117119
~Result() noexcept { Destroy(); }
118120

@@ -301,8 +303,8 @@ class [[nodiscard]] Result : public util::EqualityComparable<Result<T>> {
301303
/// \return The stored non-OK status object, or an OK status if this object
302304
/// has a value.
303305
Status status() && {
304-
if (ok()) return Status::OK();
305-
auto tmp = Status::UnknownError("Uninitialized Result<T>");
306+
if (ARROW_PREDICT_TRUE(ok())) return Status::OK();
307+
auto tmp = internal::UninitializedResult();
306308
std::swap(status_, tmp);
307309
return tmp;
308310
}

cpp/src/arrow/status.cc

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
#include <cassert>
1616
#include <cstdlib>
1717
#include <iostream>
18-
#include <sstream>
18+
#ifdef ARROW_EXTRA_ERROR_CONTEXT
19+
# include <sstream>
20+
#endif
1921

2022
#include "arrow/util/logging.h"
2123

@@ -26,18 +28,19 @@ Status::Status(StatusCode code, const std::string& msg)
2628

2729
Status::Status(StatusCode code, std::string msg, std::shared_ptr<StatusDetail> detail) {
2830
ARROW_CHECK_NE(code, StatusCode::OK) << "Cannot construct ok status with message";
29-
state_ = new State;
30-
state_->code = code;
31-
state_->msg = std::move(msg);
32-
if (detail != nullptr) {
33-
state_->detail = std::move(detail);
34-
}
31+
state_ = new State{code, /*is_constant=*/false, std::move(msg), std::move(detail)};
3532
}
3633

3734
void Status::CopyFrom(const Status& s) {
38-
delete state_;
35+
if (ARROW_PREDICT_FALSE(state_ != NULL)) {
36+
if (!state_->is_constant) {
37+
DeleteState();
38+
}
39+
}
3940
if (s.state_ == nullptr) {
4041
state_ = nullptr;
42+
} else if (s.state_->is_constant) {
43+
state_ = s.state_;
4144
} else {
4245
state_ = new State(*s.state_);
4346
}
@@ -160,6 +163,10 @@ void Status::AddContextLine(const char* filename, int line, const char* expr) {
160163
ARROW_CHECK(!ok()) << "Cannot add context line to ok status";
161164
std::stringstream ss;
162165
ss << "\n" << filename << ":" << line << " " << expr;
166+
if (state_->is_constant) {
167+
// We can't add context lines to a StatusConstant's state, so copy it now
168+
state_ = new State{code(), /*is_constant=*/false, message(), detail()};
169+
}
163170
state_->msg += ss.str();
164171
}
165172
#endif

cpp/src/arrow/status.h

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@
8282
#endif
8383

8484
namespace arrow {
85+
namespace internal {
86+
class StatusConstant;
87+
}
8588

8689
enum class StatusCode : char {
8790
OK = 0,
@@ -135,10 +138,10 @@ class ARROW_EXPORT [[nodiscard]] Status : public util::EqualityComparable<Status
135138
// Create a success status.
136139
constexpr Status() noexcept : state_(NULLPTR) {}
137140
~Status() noexcept {
138-
// ARROW-2400: On certain compilers, splitting off the slow path improves
139-
// performance significantly.
140141
if (ARROW_PREDICT_FALSE(state_ != NULL)) {
141-
DeleteState();
142+
if (!state_->is_constant) {
143+
DeleteState();
144+
}
142145
}
143146
}
144147

@@ -366,29 +369,36 @@ class ARROW_EXPORT [[nodiscard]] Status : public util::EqualityComparable<Status
366369
private:
367370
struct State {
368371
StatusCode code;
372+
bool is_constant;
369373
std::string msg;
370374
std::shared_ptr<StatusDetail> detail;
371375
};
372376
// OK status has a `NULL` state_. Otherwise, `state_` points to
373377
// a `State` structure containing the error code and message(s)
374378
State* state_;
375379

376-
void DeleteState() {
380+
void DeleteState() noexcept {
381+
// ARROW-2400: On certain compilers, splitting off the slow path improves
382+
// performance significantly.
377383
delete state_;
378-
state_ = NULLPTR;
379384
}
380385
void CopyFrom(const Status& s);
381386
inline void MoveFrom(Status& s);
387+
388+
friend class internal::StatusConstant;
382389
};
383390

384391
void Status::MoveFrom(Status& s) {
385-
delete state_;
392+
if (ARROW_PREDICT_FALSE(state_ != NULL)) {
393+
if (!state_->is_constant) {
394+
DeleteState();
395+
}
396+
}
386397
state_ = s.state_;
387398
s.state_ = NULLPTR;
388399
}
389400

390-
Status::Status(const Status& s)
391-
: state_((s.state_ == NULLPTR) ? NULLPTR : new State(*s.state_)) {}
401+
Status::Status(const Status& s) : state_{NULLPTR} { CopyFrom(s); }
392402

393403
Status& Status::operator=(const Status& s) {
394404
// The following condition catches both aliasing (when this == &s),

cpp/src/arrow/status_internal.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#pragma once
19+
20+
#include "arrow/status.h"
21+
#include "arrow/util/logging.h"
22+
23+
namespace arrow::internal {
24+
25+
class StatusConstant {
26+
public:
27+
StatusConstant(StatusCode code, std::string msg,
28+
std::shared_ptr<StatusDetail> detail = nullptr)
29+
: state_{code, /*is_constant=*/true, std::move(msg), std::move(detail)} {
30+
ARROW_CHECK_NE(code, StatusCode::OK)
31+
<< "StatusConstant is not intended for use with OK status codes";
32+
}
33+
34+
operator Status() { // NOLINT(runtime/explicit)
35+
Status st;
36+
st.state_ = &state_;
37+
return st;
38+
}
39+
40+
private:
41+
Status::State state_;
42+
};
43+
44+
} // namespace arrow::internal

cpp/src/arrow/status_test.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <gtest/gtest.h>
2222

2323
#include "arrow/status.h"
24+
#include "arrow/status_internal.h"
2425
#include "arrow/testing/gtest_util.h"
2526
#include "arrow/testing/matchers.h"
2627

@@ -76,6 +77,27 @@ TEST(StatusTest, TestCoverageWarnNotOK) {
7677
ARROW_WARN_NOT_OK(Status::Invalid("invalid"), "Expected warning");
7778
}
7879

80+
TEST(StatusTest, StatusConstant) {
81+
internal::StatusConstant constant{StatusCode::Invalid, "default error"};
82+
Status st = constant;
83+
84+
ASSERT_EQ(st.code(), StatusCode::Invalid);
85+
ASSERT_EQ(st.message(), "default error");
86+
ASSERT_EQ(st.detail(), nullptr);
87+
88+
Status copy = st;
89+
ASSERT_EQ(&st.message(), &copy.message());
90+
Status moved = std::move(st);
91+
ASSERT_EQ(&copy.message(), &moved.message());
92+
ASSERT_OK(st);
93+
94+
Status other = constant;
95+
ASSERT_EQ(other.code(), StatusCode::Invalid);
96+
ASSERT_EQ(other.message(), "default error");
97+
ASSERT_EQ(other.detail(), nullptr);
98+
ASSERT_EQ(&other.message(), &moved.message());
99+
}
100+
79101
TEST(StatusTest, AndStatus) {
80102
Status a = Status::OK();
81103
Status b = Status::OK();

0 commit comments

Comments
 (0)