Replies: 2 comments
|
This failure is happening on the client side, before the BigQuery Storage service sees the The default BigQuery Read retry traits only retry I would also upgrade from v2.22.0 before adding custom policy. A custom |
|
That string isn't ours. At } else {
auto err = GRPC_ERROR_CREATE_REFERENCING(
"Error occurred when fetching oauth2 token.", &error, 1);
pending_request->result = grpc_error_to_absl_status(err);
}and the macro hardcodes the code: #define GRPC_ERROR_CREATE_REFERENCING(desc, errs, count) \
grpc_status_create(absl::StatusCode::kUnknown, desc, DEBUG_LOCATION, count, \
errs)UNKNOWN there isn't a mapping of the underlying failure, it's a literal. That's the whole reason it doesn't arrive as UNAVAILABLE. That exact text also pins your gRPC at 1.66 or older, since the site is gone in 1.67. For reference, v2.22.0's Bazel pin is gRPC 1.62.0, and the code above is identical in 1.60 through 1.66. @hamedrabah is right that it's client side, but the mechanism is a little different and the difference matters for debugging. If you're on default ADC, the auth decorator isn't in your stub stack at all. From if (auth->RequiresConfigureContext()) {
stub = std::make_shared<BigQueryReadAuth>(std::move(auth), std::move(stub));
}and void visit(GoogleDefaultCredentialsConfig const&) override {
result = std::make_unique<GrpcChannelCredentialsAuthentication>(
grpc::GoogleDefaultCredentials());
}whose Conditions: gRPC caches the token and only refetches when it's within The reason you can't tell those apart from the status is that on this gRPC a transport failure and a non-200 collapse into the same code and the same text, since Your logs do separate them, and it's one or the other, never both. Transport failures go through GRPC_LOG_IF_ERROR("oauth_fetch", error);Non-200s do not, because if (response->status != 200) {
gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].",
response->status,
null_terminated_body != nullptr ? null_terminated_body : "");Both are Another free discriminator: if your ADC resolves to a service account JSON key, gRPC's google default creds build self-signed JWTs via On retry, the default is static inline bool IsPermanentFailure(google::cloud::Status const& status) {
return status.code() != StatusCode::kOk &&
status.code() != StatusCode::kUnavailable;
}so yes, UNKNOWN is permanent. But there's a wrinkle in the resume loop that explains why your message says 0 rows specifically. From while (!retry_policy->IsExhausted() &&
(has_received_data_ || retry_policy->OnFailure(last_status))) {
Resumption itself is offset-based, so retrying is safe. The whole updater is: void BigQueryReadReadRowsStreamingUpdater(
::google::cloud::bigquery::storage::v1::ReadRowsResponse const& response,
::google::cloud::bigquery::storage::v1::ReadRowsRequest& request) {
request.set_offset(request.offset() + response.row_count());
}Supported way to widen it, no internal headers needed: namespace bqs = ::google::cloud::bigquery_storage_v1;
using ::google::cloud::Status;
using ::google::cloud::StatusCode;
class RetryUnknownPolicy : public bqs::BigQueryReadRetryPolicy {
public:
explicit RetryUnknownPolicy(int maximum_failures)
: maximum_failures_(maximum_failures) {}
bool IsPermanentFailure(Status const& s) const override {
auto const c = s.code();
return c != StatusCode::kOk && c != StatusCode::kUnavailable &&
c != StatusCode::kUnknown;
}
bool OnFailure(Status const& s) override {
if (IsPermanentFailure(s)) return false;
++failures_;
return !IsExhausted();
}
bool IsExhausted() const override { return failures_ > maximum_failures_; }
std::unique_ptr<bqs::BigQueryReadRetryPolicy> clone() const override {
return std::make_unique<RetryUnknownPolicy>(maximum_failures_);
}
private:
int maximum_failures_;
int failures_ = 0;
};That's the same shape as Scope it to the one call rather than the connection, auto rows = client.ReadRows(
request, google::cloud::Options{}
.set<bqs::BigQueryReadRetryPolicyOption>(
RetryUnknownPolicy(3).clone()));Set it connection-wide instead and A newer gRPC helps, but less than you'd hope, so I wouldn't skip the policy on that basis. 1.67 did replace the error site, and a non-200 or unparseable response now becomes if (!error.ok()) {
self->on_done_(std::move(error));
return;
}and if (!error.ok()) {
// TODO(roth): It shouldn't be necessary to explicitly set the
// status to UNAVAILABLE here. Once the HTTP client code is
// migrated to stop using legacy grpc_error APIs to create
// statuses, we should be able to just propagate the status as-is.
self->on_done_(absl::UnavailableError(StatusToString(error)));
return;
}
if (self->response_.status != 200) {
grpc_status_code status_code =
grpc_http2_status_to_grpc_status(self->response_.status);
if (status_code != GRPC_STATUS_UNAVAILABLE) {
status_code = GRPC_STATUS_UNAUTHENTICATED;
}So check what you actually link against, because google-cloud-cpp's own pin is behind that. v2.22.0 pins 1.62.0 and even v3.8.0 pins 1.74.1. Bumping google-cloud-cpp alone won't get you the deliberate mapping. Last thing, the |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Client
google-cloud-cpp v2.22.0What Happened
I want to understand the failure mode here.
Is
UNKNOWNerror type retried by the defaultBigQueryReadConnectionretry policy? If a transient token fetch failure lands asUNKNOWN, it looks like it would bypass retry and kill the read outright. Is that expected, and is there a supported way to make token-fetch failures retryable ?All reactions