priority.cc
Go to the documentation of this file.
1 //
2 // Copyright 2018 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
18 
19 #include <inttypes.h>
20 #include <limits.h>
21 #include <stddef.h>
22 
23 #include <algorithm>
24 #include <map>
25 #include <memory>
26 #include <string>
27 #include <utility>
28 #include <vector>
29 
30 #include "absl/memory/memory.h"
31 #include "absl/status/status.h"
32 #include "absl/status/statusor.h"
33 #include "absl/strings/str_cat.h"
34 #include "absl/strings/string_view.h"
35 
38 #include <grpc/support/log.h>
39 
59 #include "src/core/lib/json/json.h"
62 
63 namespace grpc_core {
64 
65 TraceFlag grpc_lb_priority_trace(false, "priority_lb");
66 
67 namespace {
68 
69 constexpr char kPriority[] = "priority_experimental";
70 
71 // How long we keep a child around for after it is no longer being used
72 // (either because it has been removed from the config or because we
73 // have switched to a higher-priority child).
74 constexpr Duration kChildRetentionInterval = Duration::Minutes(15);
75 
76 // Default for how long we wait for a newly created child to get connected
77 // before starting to attempt the next priority. Overridable via channel arg.
78 constexpr Duration kDefaultChildFailoverTimeout = Duration::Seconds(10);
79 
80 // Config for priority LB policy.
81 class PriorityLbConfig : public LoadBalancingPolicy::Config {
82  public:
83  struct PriorityLbChild {
84  RefCountedPtr<LoadBalancingPolicy::Config> config;
86  };
87 
88  PriorityLbConfig(std::map<std::string, PriorityLbChild> children,
89  std::vector<std::string> priorities)
90  : children_(std::move(children)), priorities_(std::move(priorities)) {}
91 
92  const char* name() const override { return kPriority; }
93 
94  const std::map<std::string, PriorityLbChild>& children() const {
95  return children_;
96  }
97  const std::vector<std::string>& priorities() const { return priorities_; }
98 
99  private:
100  const std::map<std::string, PriorityLbChild> children_;
101  const std::vector<std::string> priorities_;
102 };
103 
104 // priority LB policy.
105 class PriorityLb : public LoadBalancingPolicy {
106  public:
107  explicit PriorityLb(Args args);
108 
109  const char* name() const override { return kPriority; }
110 
111  void UpdateLocked(UpdateArgs args) override;
112  void ExitIdleLocked() override;
113  void ResetBackoffLocked() override;
114 
115  private:
116  // Each ChildPriority holds a ref to the PriorityLb.
117  class ChildPriority : public InternallyRefCounted<ChildPriority> {
118  public:
119  ChildPriority(RefCountedPtr<PriorityLb> priority_policy, std::string name);
120 
121  ~ChildPriority() override {
122  priority_policy_.reset(DEBUG_LOCATION, "ChildPriority");
123  }
124 
125  const std::string& name() const { return name_; }
126 
127  void UpdateLocked(RefCountedPtr<LoadBalancingPolicy::Config> config,
129  void ExitIdleLocked();
130  void ResetBackoffLocked();
131  void MaybeDeactivateLocked();
132  void MaybeReactivateLocked();
133 
134  void Orphan() override;
135 
136  std::unique_ptr<SubchannelPicker> GetPicker();
137 
138  grpc_connectivity_state connectivity_state() const {
139  return connectivity_state_;
140  }
141 
142  const absl::Status& connectivity_status() const {
143  return connectivity_status_;
144  }
145 
146  bool FailoverTimerPending() const { return failover_timer_ != nullptr; }
147 
148  private:
149  // A simple wrapper for ref-counting a picker from the child policy.
150  class RefCountedPicker : public RefCounted<RefCountedPicker> {
151  public:
152  explicit RefCountedPicker(std::unique_ptr<SubchannelPicker> picker)
153  : picker_(std::move(picker)) {}
154  PickResult Pick(PickArgs args) { return picker_->Pick(args); }
155 
156  private:
157  std::unique_ptr<SubchannelPicker> picker_;
158  };
159 
160  // A non-ref-counted wrapper for RefCountedPicker.
161  class RefCountedPickerWrapper : public SubchannelPicker {
162  public:
163  explicit RefCountedPickerWrapper(RefCountedPtr<RefCountedPicker> picker)
164  : picker_(std::move(picker)) {}
165  PickResult Pick(PickArgs args) override { return picker_->Pick(args); }
166 
167  private:
168  RefCountedPtr<RefCountedPicker> picker_;
169  };
170 
171  class Helper : public ChannelControlHelper {
172  public:
173  explicit Helper(RefCountedPtr<ChildPriority> priority)
174  : priority_(std::move(priority)) {}
175 
176  ~Helper() override { priority_.reset(DEBUG_LOCATION, "Helper"); }
177 
178  RefCountedPtr<SubchannelInterface> CreateSubchannel(
179  ServerAddress address, const grpc_channel_args& args) override;
180  void UpdateState(grpc_connectivity_state state,
181  const absl::Status& status,
182  std::unique_ptr<SubchannelPicker> picker) override;
183  void RequestReresolution() override;
184  absl::string_view GetAuthority() override;
185  void AddTraceEvent(TraceSeverity severity,
186  absl::string_view message) override;
187 
188  private:
189  RefCountedPtr<ChildPriority> priority_;
190  };
191 
192  class DeactivationTimer : public InternallyRefCounted<DeactivationTimer> {
193  public:
194  explicit DeactivationTimer(RefCountedPtr<ChildPriority> child_priority);
195 
196  void Orphan() override;
197 
198  private:
199  static void OnTimer(void* arg, grpc_error_handle error);
200  void OnTimerLocked(grpc_error_handle);
201 
202  RefCountedPtr<ChildPriority> child_priority_;
205  bool timer_pending_ = true;
206  };
207 
208  class FailoverTimer : public InternallyRefCounted<FailoverTimer> {
209  public:
210  explicit FailoverTimer(RefCountedPtr<ChildPriority> child_priority);
211 
212  void Orphan() override;
213 
214  private:
215  static void OnTimer(void* arg, grpc_error_handle error);
216  void OnTimerLocked(grpc_error_handle);
217 
218  RefCountedPtr<ChildPriority> child_priority_;
221  bool timer_pending_ = true;
222  };
223 
224  // Methods for dealing with the child policy.
225  OrphanablePtr<LoadBalancingPolicy> CreateChildPolicyLocked(
226  const grpc_channel_args* args);
227 
228  void OnConnectivityStateUpdateLocked(
230  std::unique_ptr<SubchannelPicker> picker);
231 
232  RefCountedPtr<PriorityLb> priority_policy_;
235 
236  OrphanablePtr<LoadBalancingPolicy> child_policy_;
237 
240  RefCountedPtr<RefCountedPicker> picker_wrapper_;
241 
243 
244  OrphanablePtr<DeactivationTimer> deactivation_timer_;
245  OrphanablePtr<FailoverTimer> failover_timer_;
246  };
247 
248  ~PriorityLb() override;
249 
250  void ShutdownLocked() override;
251 
252  // Returns the priority of the specified child name, or UINT32_MAX if
253  // the child is not in the current priority list.
254  uint32_t GetChildPriorityLocked(const std::string& child_name) const;
255 
256  // Called when a child's connectivity state has changed.
257  // May propagate the update to the channel or trigger choosing a new
258  // priority.
259  void HandleChildConnectivityStateChangeLocked(ChildPriority* child);
260 
261  // Deletes a child. Called when the child's deactivation timer fires.
262  void DeleteChild(ChildPriority* child);
263 
264  // Iterates through the list of priorities to choose one:
265  // - If the child for a priority doesn't exist, creates it.
266  // - If a child's failover timer is pending, selects that priority
267  // while we wait for the child to attempt to connect.
268  // - If the child is connected, selects that priority.
269  // - Otherwise, continues on to the next child.
270  // Delegates to the last child if none of the children are connecting.
271  // Reports TRANSIENT_FAILURE if the priority list is empty.
272  //
273  // This method is idempotent; it should yield the same result every
274  // time as a function of the state of the children.
275  void ChoosePriorityLocked();
276 
277  // Sets the specified priority as the current priority.
278  // Deactivates any children at lower priorities.
279  // Returns the child's picker to the channel.
280  void SetCurrentPriorityLocked(uint32_t priority);
281 
283 
284  // Current channel args and config from the resolver.
285  const grpc_channel_args* args_ = nullptr;
286  RefCountedPtr<PriorityLbConfig> config_;
288 
289  // Internal state.
290  bool shutting_down_ = false;
291 
292  bool update_in_progress_ = false;
293 
294  // All children that currently exist.
295  // Some of these children may be in deactivated state.
296  std::map<std::string, OrphanablePtr<ChildPriority>> children_;
297  // The priority that is being used.
299  // Points to the current child from before the most recent update.
300  // We will continue to use this child until we decide which of the new
301  // children to use.
302  ChildPriority* current_child_from_before_update_ = nullptr;
303 };
304 
305 //
306 // PriorityLb
307 //
308 
309 PriorityLb::PriorityLb(Args args)
310  : LoadBalancingPolicy(std::move(args)),
314  {static_cast<int>(kDefaultChildFailoverTimeout.millis()), 0,
315  INT_MAX}))) {
317  gpr_log(GPR_INFO, "[priority_lb %p] created", this);
318  }
319 }
320 
321 PriorityLb::~PriorityLb() {
323  gpr_log(GPR_INFO, "[priority_lb %p] destroying priority LB policy", this);
324  }
326 }
327 
328 void PriorityLb::ShutdownLocked() {
330  gpr_log(GPR_INFO, "[priority_lb %p] shutting down", this);
331  }
332  shutting_down_ = true;
333  children_.clear();
334 }
335 
337  if (current_priority_ != UINT32_MAX) {
338  const std::string& child_name = config_->priorities()[current_priority_];
341  "[priority_lb %p] exiting IDLE for current priority %d child %s",
342  this, current_priority_, child_name.c_str());
343  }
344  children_[child_name]->ExitIdleLocked();
345  }
346 }
347 
348 void PriorityLb::ResetBackoffLocked() {
349  for (const auto& p : children_) p.second->ResetBackoffLocked();
350 }
351 
352 void PriorityLb::UpdateLocked(UpdateArgs args) {
354  gpr_log(GPR_INFO, "[priority_lb %p] received update", this);
355  }
356  // Save current child.
357  if (current_priority_ != UINT32_MAX) {
358  const std::string& child_name = config_->priorities()[current_priority_];
359  auto* child = children_[child_name].get();
360  GPR_ASSERT(child != nullptr);
361  if (child->connectivity_state() == GRPC_CHANNEL_READY) {
362  current_child_from_before_update_ = children_[child_name].get();
363  }
364  }
365  // Update config.
366  config_ = std::move(args.config);
367  // Update args.
369  args_ = args.args;
370  args.args = nullptr;
371  // Update addresses.
373  // Check all existing children against the new config.
374  update_in_progress_ = true;
375  for (const auto& p : children_) {
376  const std::string& child_name = p.first;
377  auto& child = p.second;
378  auto config_it = config_->children().find(child_name);
379  if (config_it == config_->children().end()) {
380  // Existing child not found in new config. Deactivate it.
381  child->MaybeDeactivateLocked();
382  } else {
383  // Existing child found in new config. Update it.
384  child->UpdateLocked(config_it->second.config,
385  config_it->second.ignore_reresolution_requests);
386  }
387  }
388  update_in_progress_ = false;
389  // Try to get connected.
390  ChoosePriorityLocked();
391 }
392 
393 uint32_t PriorityLb::GetChildPriorityLocked(
394  const std::string& child_name) const {
395  for (uint32_t priority = 0; priority < config_->priorities().size();
396  ++priority) {
397  if (config_->priorities()[priority] == child_name) return priority;
398  }
399  return UINT32_MAX;
400 }
401 
402 void PriorityLb::HandleChildConnectivityStateChangeLocked(
403  ChildPriority* child) {
404  // If we're in the process of propagating an update from our parent to
405  // our children, ignore any updates that come from the children. We
406  // will instead choose a new priority once the update has been seen by
407  // all children. This ensures that we don't incorrectly do the wrong
408  // thing while state is inconsistent.
409  if (update_in_progress_) return;
410  // Special case for the child that was the current child before the
411  // most recent update.
415  "[priority_lb %p] state update for current child from before "
416  "config update",
417  this);
418  }
419  if (child->connectivity_state() == GRPC_CHANNEL_READY ||
420  child->connectivity_state() == GRPC_CHANNEL_IDLE) {
421  // If it's still READY or IDLE, we stick with this child, so pass
422  // the new picker up to our parent.
423  channel_control_helper()->UpdateState(child->connectivity_state(),
424  child->connectivity_status(),
425  child->GetPicker());
426  } else {
427  // If it's no longer READY or IDLE, we should stop using it.
428  // We already started trying other priorities as a result of the
429  // update, but calling ChoosePriorityLocked() ensures that we will
430  // properly select between CONNECTING and TRANSIENT_FAILURE as the
431  // new state to report to our parent.
433  ChoosePriorityLocked();
434  }
435  return;
436  }
437  // Otherwise, find the child's priority.
438  uint32_t child_priority = GetChildPriorityLocked(child->name());
441  "[priority_lb %p] state update for priority %u, child %s, current "
442  "priority %u",
443  this, child_priority, child->name().c_str(), current_priority_);
444  }
445  // Unconditionally call ChoosePriorityLocked(). It should do the
446  // right thing based on the state of all children.
447  ChoosePriorityLocked();
448 }
449 
450 void PriorityLb::DeleteChild(ChildPriority* child) {
451  // If this was the current child from before the most recent update,
452  // stop using it. We already started trying other priorities as a
453  // result of the update, but calling ChoosePriorityLocked() ensures that
454  // we will properly select between CONNECTING and TRANSIENT_FAILURE as the
455  // new state to report to our parent.
458  ChoosePriorityLocked();
459  }
460  children_.erase(child->name());
461 }
462 
463 void PriorityLb::ChoosePriorityLocked() {
464  // If priority list is empty, report TF.
465  if (config_->priorities().empty()) {
468  absl::UnavailableError("priority policy has empty priority list");
469  channel_control_helper()->UpdateState(
471  absl::make_unique<TransientFailurePicker>(status));
472  return;
473  }
474  // Iterate through priorities, searching for one in READY or IDLE,
475  // creating new children as needed.
477  for (uint32_t priority = 0; priority < config_->priorities().size();
478  ++priority) {
479  // If the child for the priority does not exist yet, create it.
480  const std::string& child_name = config_->priorities()[priority];
482  gpr_log(GPR_INFO, "[priority_lb %p] trying priority %u, child %s", this,
483  priority, child_name.c_str());
484  }
485  auto& child = children_[child_name];
486  if (child == nullptr) {
487  // If we're not still using an old child from before the last
488  // update, report CONNECTING here.
489  // This is probably not strictly necessary, since the child should
490  // immediately report CONNECTING and cause us to report that state
491  // anyway, but we do this just in case the child fails to report
492  // state before UpdateLocked() returns.
493  if (current_child_from_before_update_ != nullptr) {
494  channel_control_helper()->UpdateState(
496  absl::make_unique<QueuePicker>(Ref(DEBUG_LOCATION, "QueuePicker")));
497  }
499  child = MakeOrphanable<ChildPriority>(
500  Ref(DEBUG_LOCATION, "ChildPriority"), child_name);
501  auto child_config = config_->children().find(child_name);
502  GPR_DEBUG_ASSERT(child_config != config_->children().end());
503  child->UpdateLocked(child_config->second.config,
504  child_config->second.ignore_reresolution_requests);
505  return;
506  }
507  // The child already exists.
508  child->MaybeReactivateLocked();
509  // If the child is in state READY or IDLE, switch to it.
510  if (child->connectivity_state() == GRPC_CHANNEL_READY ||
511  child->connectivity_state() == GRPC_CHANNEL_IDLE) {
512  SetCurrentPriorityLocked(priority);
513  return;
514  }
515  // Child is not READY or IDLE.
516  // If its failover timer is still pending, give it time to fire.
517  if (child->FailoverTimerPending()) {
520  "[priority_lb %p] priority %u, child %s: child still "
521  "attempting to connect, will wait",
522  this, priority, child_name.c_str());
523  }
525  // If we're not still using an old child from before the last
526  // update, report CONNECTING here.
527  if (current_child_from_before_update_ != nullptr) {
528  channel_control_helper()->UpdateState(child->connectivity_state(),
529  child->connectivity_status(),
530  child->GetPicker());
531  }
532  return;
533  }
534  // Child has been failing for a while. Move on to the next priority.
537  "[priority_lb %p] skipping priority %u, child %s: state=%s, "
538  "failover timer not pending",
539  this, priority, child_name.c_str(),
540  ConnectivityStateName(child->connectivity_state()));
541  }
542  }
543  // If we didn't find any priority to try, pick the first one in state
544  // CONNECTING.
547  "[priority_lb %p] no priority reachable, checking for CONNECTING "
548  "priority to delegate to",
549  this);
550  }
551  for (uint32_t priority = 0; priority < config_->priorities().size();
552  ++priority) {
553  // If the child for the priority does not exist yet, create it.
554  const std::string& child_name = config_->priorities()[priority];
556  gpr_log(GPR_INFO, "[priority_lb %p] trying priority %u, child %s", this,
557  priority, child_name.c_str());
558  }
559  auto& child = children_[child_name];
560  GPR_ASSERT(child != nullptr);
561  if (child->connectivity_state() == GRPC_CHANNEL_CONNECTING) {
562  channel_control_helper()->UpdateState(child->connectivity_state(),
563  child->connectivity_status(),
564  child->GetPicker());
565  return;
566  }
567  }
568  // Did not find any child in CONNECTING, delegate to last child.
569  const std::string& child_name = config_->priorities().back();
572  "[priority_lb %p] no priority in CONNECTING, delegating to "
573  "lowest priority child %s",
574  this, child_name.c_str());
575  }
576  auto& child = children_[child_name];
577  GPR_ASSERT(child != nullptr);
578  channel_control_helper()->UpdateState(child->connectivity_state(),
579  child->connectivity_status(),
580  child->GetPicker());
581 }
582 
583 void PriorityLb::SetCurrentPriorityLocked(uint32_t priority) {
585  gpr_log(GPR_INFO, "[priority_lb %p] selected priority %u, child %s", this,
586  priority, config_->priorities()[priority].c_str());
587  }
590  // Deactivate lower priorities.
591  for (uint32_t p = priority + 1; p < config_->priorities().size(); ++p) {
592  const std::string& child_name = config_->priorities()[p];
593  auto it = children_.find(child_name);
594  if (it != children_.end()) it->second->MaybeDeactivateLocked();
595  }
596  // Update picker.
597  auto& child = children_[config_->priorities()[priority]];
598  channel_control_helper()->UpdateState(child->connectivity_state(),
599  child->connectivity_status(),
600  child->GetPicker());
601 }
602 
603 //
604 // PriorityLb::ChildPriority::DeactivationTimer
605 //
606 
607 PriorityLb::ChildPriority::DeactivationTimer::DeactivationTimer(
608  RefCountedPtr<PriorityLb::ChildPriority> child_priority)
609  : child_priority_(std::move(child_priority)) {
612  "[priority_lb %p] child %s (%p): deactivating -- will remove in "
613  "%" PRId64 "ms",
614  child_priority_->priority_policy_.get(),
615  child_priority_->name_.c_str(), child_priority_.get(),
616  kChildRetentionInterval.millis());
617  }
618  GRPC_CLOSURE_INIT(&on_timer_, OnTimer, this, nullptr);
619  Ref(DEBUG_LOCATION, "Timer").release();
620  grpc_timer_init(&timer_, ExecCtx::Get()->Now() + kChildRetentionInterval,
621  &on_timer_);
622 }
623 
624 void PriorityLb::ChildPriority::DeactivationTimer::Orphan() {
625  if (timer_pending_) {
627  gpr_log(GPR_INFO, "[priority_lb %p] child %s (%p): reactivating",
628  child_priority_->priority_policy_.get(),
629  child_priority_->name_.c_str(), child_priority_.get());
630  }
631  timer_pending_ = false;
633  }
634  Unref();
635 }
636 
637 void PriorityLb::ChildPriority::DeactivationTimer::OnTimer(
638  void* arg, grpc_error_handle error) {
639  auto* self = static_cast<DeactivationTimer*>(arg);
640  (void)GRPC_ERROR_REF(error); // ref owned by lambda
641  self->child_priority_->priority_policy_->work_serializer()->Run(
642  [self, error]() { self->OnTimerLocked(error); }, DEBUG_LOCATION);
643 }
644 
645 void PriorityLb::ChildPriority::DeactivationTimer::OnTimerLocked(
650  "[priority_lb %p] child %s (%p): deactivation timer fired, "
651  "deleting child",
652  child_priority_->priority_policy_.get(),
653  child_priority_->name_.c_str(), child_priority_.get());
654  }
655  timer_pending_ = false;
656  child_priority_->priority_policy_->DeleteChild(child_priority_.get());
657  }
658  Unref(DEBUG_LOCATION, "Timer");
660 }
661 
662 //
663 // PriorityLb::ChildPriority::FailoverTimer
664 //
665 
666 PriorityLb::ChildPriority::FailoverTimer::FailoverTimer(
667  RefCountedPtr<PriorityLb::ChildPriority> child_priority)
668  : child_priority_(std::move(child_priority)) {
670  gpr_log(
671  GPR_INFO,
672  "[priority_lb %p] child %s (%p): starting failover timer for %" PRId64
673  "ms",
674  child_priority_->priority_policy_.get(), child_priority_->name_.c_str(),
675  child_priority_.get(),
676  child_priority_->priority_policy_->child_failover_timeout_.millis());
677  }
678  GRPC_CLOSURE_INIT(&on_timer_, OnTimer, this, nullptr);
679  Ref(DEBUG_LOCATION, "Timer").release();
681  &timer_,
682  ExecCtx::Get()->Now() +
683  child_priority_->priority_policy_->child_failover_timeout_,
684  &on_timer_);
685 }
686 
687 void PriorityLb::ChildPriority::FailoverTimer::Orphan() {
688  if (timer_pending_) {
691  "[priority_lb %p] child %s (%p): cancelling failover timer",
692  child_priority_->priority_policy_.get(),
693  child_priority_->name_.c_str(), child_priority_.get());
694  }
695  timer_pending_ = false;
697  }
698  Unref();
699 }
700 
701 void PriorityLb::ChildPriority::FailoverTimer::OnTimer(
702  void* arg, grpc_error_handle error) {
703  auto* self = static_cast<FailoverTimer*>(arg);
704  (void)GRPC_ERROR_REF(error); // ref owned by lambda
705  self->child_priority_->priority_policy_->work_serializer()->Run(
706  [self, error]() { self->OnTimerLocked(error); }, DEBUG_LOCATION);
707 }
708 
709 void PriorityLb::ChildPriority::FailoverTimer::OnTimerLocked(
714  "[priority_lb %p] child %s (%p): failover timer fired, "
715  "reporting TRANSIENT_FAILURE",
716  child_priority_->priority_policy_.get(),
717  child_priority_->name_.c_str(), child_priority_.get());
718  }
719  timer_pending_ = false;
720  child_priority_->OnConnectivityStateUpdateLocked(
722  absl::Status(absl::StatusCode::kUnavailable, "failover timer fired"),
723  nullptr);
724  }
725  Unref(DEBUG_LOCATION, "Timer");
727 }
728 
729 //
730 // PriorityLb::ChildPriority
731 //
732 
733 PriorityLb::ChildPriority::ChildPriority(
734  RefCountedPtr<PriorityLb> priority_policy, std::string name)
735  : priority_policy_(std::move(priority_policy)), name_(std::move(name)) {
737  gpr_log(GPR_INFO, "[priority_lb %p] creating child %s (%p)",
738  priority_policy_.get(), name_.c_str(), this);
739  }
740  // Start the failover timer.
741  failover_timer_ = MakeOrphanable<FailoverTimer>(Ref());
742 }
743 
744 void PriorityLb::ChildPriority::Orphan() {
746  gpr_log(GPR_INFO, "[priority_lb %p] child %s (%p): orphaned",
747  priority_policy_.get(), name_.c_str(), this);
748  }
749  failover_timer_.reset();
750  deactivation_timer_.reset();
751  // Remove the child policy's interested_parties pollset_set from the
752  // xDS policy.
753  grpc_pollset_set_del_pollset_set(child_policy_->interested_parties(),
754  priority_policy_->interested_parties());
755  child_policy_.reset();
756  // Drop our ref to the child's picker, in case it's holding a ref to
757  // the child.
758  picker_wrapper_.reset();
759  Unref(DEBUG_LOCATION, "ChildPriority+Orphan");
760 }
761 
762 std::unique_ptr<LoadBalancingPolicy::SubchannelPicker>
763 PriorityLb::ChildPriority::GetPicker() {
764  if (picker_wrapper_ == nullptr) {
765  return absl::make_unique<QueuePicker>(
766  priority_policy_->Ref(DEBUG_LOCATION, "QueuePicker"));
767  }
768  return absl::make_unique<RefCountedPickerWrapper>(picker_wrapper_);
769 }
770 
771 void PriorityLb::ChildPriority::UpdateLocked(
772  RefCountedPtr<LoadBalancingPolicy::Config> config,
774  if (priority_policy_->shutting_down_) return;
776  gpr_log(GPR_INFO, "[priority_lb %p] child %s (%p): start update",
777  priority_policy_.get(), name_.c_str(), this);
778  }
780  // Create policy if needed.
781  if (child_policy_ == nullptr) {
782  child_policy_ = CreateChildPolicyLocked(priority_policy_->args_);
783  }
784  // Construct update args.
785  UpdateArgs update_args;
786  update_args.config = std::move(config);
787  if (priority_policy_->addresses_.ok()) {
788  update_args.addresses = (*priority_policy_->addresses_)[name_];
789  } else {
790  update_args.addresses = priority_policy_->addresses_.status();
791  }
792  update_args.args = grpc_channel_args_copy(priority_policy_->args_);
793  // Update the policy.
796  "[priority_lb %p] child %s (%p): updating child policy handler %p",
797  priority_policy_.get(), name_.c_str(), this, child_policy_.get());
798  }
799  child_policy_->UpdateLocked(std::move(update_args));
800 }
801 
802 OrphanablePtr<LoadBalancingPolicy>
803 PriorityLb::ChildPriority::CreateChildPolicyLocked(
804  const grpc_channel_args* args) {
805  LoadBalancingPolicy::Args lb_policy_args;
806  lb_policy_args.work_serializer = priority_policy_->work_serializer();
807  lb_policy_args.args = args;
808  lb_policy_args.channel_control_helper =
809  absl::make_unique<Helper>(this->Ref(DEBUG_LOCATION, "Helper"));
810  OrphanablePtr<LoadBalancingPolicy> lb_policy =
811  MakeOrphanable<ChildPolicyHandler>(std::move(lb_policy_args),
815  "[priority_lb %p] child %s (%p): created new child policy "
816  "handler %p",
817  priority_policy_.get(), name_.c_str(), this, lb_policy.get());
818  }
819  // Add the parent's interested_parties pollset_set to that of the newly
820  // created child policy. This will make the child policy progress upon
821  // activity on the parent LB, which in turn is tied to the application's call.
822  grpc_pollset_set_add_pollset_set(lb_policy->interested_parties(),
823  priority_policy_->interested_parties());
824  return lb_policy;
825 }
826 
828  child_policy_->ExitIdleLocked();
829 }
830 
831 void PriorityLb::ChildPriority::ResetBackoffLocked() {
832  child_policy_->ResetBackoffLocked();
833 }
834 
835 void PriorityLb::ChildPriority::OnConnectivityStateUpdateLocked(
837  std::unique_ptr<SubchannelPicker> picker) {
840  "[priority_lb %p] child %s (%p): state update: %s (%s) picker %p",
841  priority_policy_.get(), name_.c_str(), this,
843  picker.get());
844  }
845  // Store the state and picker.
848  picker_wrapper_ = MakeRefCounted<RefCountedPicker>(std::move(picker));
849  // If we transition to state CONNECTING and we've not seen
850  // TRANSIENT_FAILURE more recently than READY or IDLE, start failover
851  // timer if not already pending.
852  // In any other state, update seen_ready_or_idle_since_transient_failure_
853  // and cancel failover timer.
856  failover_timer_ == nullptr) {
857  failover_timer_ = MakeOrphanable<FailoverTimer>(Ref());
858  }
859  } else if (state == GRPC_CHANNEL_READY || state == GRPC_CHANNEL_IDLE) {
861  failover_timer_.reset();
862  } else if (state == GRPC_CHANNEL_TRANSIENT_FAILURE) {
864  failover_timer_.reset();
865  }
866  // Notify the parent policy.
867  priority_policy_->HandleChildConnectivityStateChangeLocked(this);
868 }
869 
870 void PriorityLb::ChildPriority::MaybeDeactivateLocked() {
871  if (deactivation_timer_ == nullptr) {
872  deactivation_timer_ = MakeOrphanable<DeactivationTimer>(Ref());
873  }
874 }
875 
876 void PriorityLb::ChildPriority::MaybeReactivateLocked() {
877  deactivation_timer_.reset();
878 }
879 
880 //
881 // PriorityLb::ChildPriority::Helper
882 //
883 
884 RefCountedPtr<SubchannelInterface>
885 PriorityLb::ChildPriority::Helper::CreateSubchannel(
886  ServerAddress address, const grpc_channel_args& args) {
887  if (priority_->priority_policy_->shutting_down_) return nullptr;
888  return priority_->priority_policy_->channel_control_helper()
889  ->CreateSubchannel(std::move(address), args);
890 }
891 
892 void PriorityLb::ChildPriority::Helper::UpdateState(
894  std::unique_ptr<SubchannelPicker> picker) {
895  if (priority_->priority_policy_->shutting_down_) return;
896  // Notify the priority.
897  priority_->OnConnectivityStateUpdateLocked(state, status, std::move(picker));
898 }
899 
900 void PriorityLb::ChildPriority::Helper::RequestReresolution() {
901  if (priority_->priority_policy_->shutting_down_) return;
902  if (priority_->ignore_reresolution_requests_) {
903  return;
904  }
905  priority_->priority_policy_->channel_control_helper()->RequestReresolution();
906 }
907 
908 absl::string_view PriorityLb::ChildPriority::Helper::GetAuthority() {
909  return priority_->priority_policy_->channel_control_helper()->GetAuthority();
910 }
911 
912 void PriorityLb::ChildPriority::Helper::AddTraceEvent(
913  TraceSeverity severity, absl::string_view message) {
914  if (priority_->priority_policy_->shutting_down_) return;
915  priority_->priority_policy_->channel_control_helper()->AddTraceEvent(severity,
916  message);
917 }
918 
919 //
920 // factory
921 //
922 
923 class PriorityLbFactory : public LoadBalancingPolicyFactory {
924  public:
925  OrphanablePtr<LoadBalancingPolicy> CreateLoadBalancingPolicy(
926  LoadBalancingPolicy::Args args) const override {
927  return MakeOrphanable<PriorityLb>(std::move(args));
928  }
929 
930  const char* name() const override { return kPriority; }
931 
932  RefCountedPtr<LoadBalancingPolicy::Config> ParseLoadBalancingConfig(
933  const Json& json, grpc_error_handle* error) const override {
935  if (json.type() == Json::Type::JSON_NULL) {
936  // priority was mentioned as a policy in the deprecated
937  // loadBalancingPolicy field or in the client API.
939  "field:loadBalancingPolicy error:priority policy requires "
940  "configuration. Please use loadBalancingConfig field of service "
941  "config instead.");
942  return nullptr;
943  }
944  std::vector<grpc_error_handle> error_list;
945  // Children.
946  std::map<std::string, PriorityLbConfig::PriorityLbChild> children;
947  auto it = json.object_value().find("children");
948  if (it == json.object_value().end()) {
949  error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
950  "field:children error:required field missing"));
951  } else if (it->second.type() != Json::Type::OBJECT) {
952  error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
953  "field:children error:type should be object"));
954  } else {
955  const Json::Object& object = it->second.object_value();
956  for (const auto& p : object) {
957  const std::string& child_name = p.first;
958  const Json& element = p.second;
959  if (element.type() != Json::Type::OBJECT) {
960  error_list.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING(
961  absl::StrCat("field:children key:", child_name,
962  " error:should be type object")));
963  } else {
964  auto it2 = element.object_value().find("config");
965  if (it2 == element.object_value().end()) {
966  error_list.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING(
967  absl::StrCat("field:children key:", child_name,
968  " error:missing 'config' field")));
969  } else {
971  auto config = LoadBalancingPolicyRegistry::ParseLoadBalancingConfig(
972  it2->second, &parse_error);
973  bool ignore_resolution_requests = false;
974  // If present, ignore_reresolution_requests must be of type
975  // boolean.
976  auto it3 =
977  element.object_value().find("ignore_reresolution_requests");
978  if (it3 != element.object_value().end()) {
979  if (it3->second.type() == Json::Type::JSON_TRUE) {
980  ignore_resolution_requests = true;
981  } else if (it3->second.type() != Json::Type::JSON_FALSE) {
982  error_list.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING(
983  absl::StrCat("field:children key:", child_name,
984  " field:ignore_reresolution_requests:should "
985  "be type boolean")));
986  }
987  }
988  if (config == nullptr) {
990  error_list.push_back(
992  absl::StrCat("field:children key:", child_name).c_str(),
993  &parse_error, 1));
995  }
996  children[child_name].config = std::move(config);
997  children[child_name].ignore_reresolution_requests =
998  ignore_resolution_requests;
999  }
1000  }
1001  }
1002  }
1003  // Priorities.
1004  std::vector<std::string> priorities;
1005  it = json.object_value().find("priorities");
1006  if (it == json.object_value().end()) {
1007  error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
1008  "field:priorities error:required field missing"));
1009  } else if (it->second.type() != Json::Type::ARRAY) {
1010  error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
1011  "field:priorities error:type should be array"));
1012  } else {
1013  const Json::Array& array = it->second.array_value();
1014  for (size_t i = 0; i < array.size(); ++i) {
1015  const Json& element = array[i];
1016  if (element.type() != Json::Type::STRING) {
1017  error_list.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat(
1018  "field:priorities element:", i, " error:should be type string")));
1019  } else if (children.find(element.string_value()) == children.end()) {
1020  error_list.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat(
1021  "field:priorities element:", i, " error:unknown child '",
1022  element.string_value(), "'")));
1023  } else {
1024  priorities.emplace_back(element.string_value());
1025  }
1026  }
1027  if (priorities.size() != children.size()) {
1028  error_list.push_back(GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat(
1029  "field:priorities error:priorities size (", priorities.size(),
1030  ") != children size (", children.size(), ")")));
1031  }
1032  }
1033  if (error_list.empty()) {
1034  return MakeRefCounted<PriorityLbConfig>(std::move(children),
1035  std::move(priorities));
1036  } else {
1038  "priority_experimental LB policy config", &error_list);
1039  return nullptr;
1040  }
1041  }
1042 };
1043 
1044 } // namespace
1045 
1046 } // namespace grpc_core
1047 
1048 //
1049 // Plugin registration
1050 //
1051 
1055  absl::make_unique<grpc_core::PriorityLbFactory>());
1056 }
1057 
GRPC_CLOSURE_INIT
#define GRPC_CLOSURE_INIT(closure, cb, cb_arg, scheduler)
Definition: closure.h:115
trace.h
GPR_INFO
#define GPR_INFO
Definition: include/grpc/impl/codegen/log.h:56
priority_
RefCountedPtr< ChildPriority > priority_
Definition: priority.cc:189
GRPC_CHANNEL_READY
@ GRPC_CHANNEL_READY
Definition: include/grpc/impl/codegen/connectivity_state.h:36
address_filtering.h
regen-readme.it
it
Definition: regen-readme.py:15
grpc_core::LoadBalancingPolicyRegistry::Builder::RegisterLoadBalancingPolicyFactory
static void RegisterLoadBalancingPolicyFactory(std::unique_ptr< LoadBalancingPolicyFactory > factory)
Definition: lb_policy_registry.cc:87
orphanable.h
GRPC_ERROR_NONE
#define GRPC_ERROR_NONE
Definition: error.h:234
log.h
bloat_diff.severity
def severity
Definition: bloat_diff.py:143
element
static std::function< Slot &(Slot *)> element
Definition: abseil-cpp/absl/container/internal/hash_policy_traits_test.cc:44
priority
int priority
Definition: abseil-cpp/absl/synchronization/internal/graphcycles.cc:286
ignore_reresolution_requests_
bool ignore_reresolution_requests_
Definition: priority.cc:234
absl::Status::ToString
std::string ToString(StatusToStringMode mode=StatusToStringMode::kDefault) const
Definition: third_party/abseil-cpp/absl/status/status.h:821
absl::StrCat
std::string StrCat(const AlphaNum &a, const AlphaNum &b)
Definition: abseil-cpp/absl/strings/str_cat.cc:98
connectivity_state.h
GPR_DEBUG_ASSERT
#define GPR_DEBUG_ASSERT(x)
Definition: include/grpc/impl/codegen/log.h:103
failover_timer_
OrphanablePtr< FailoverTimer > failover_timer_
Definition: priority.cc:245
grpc_core
Definition: call_metric_recorder.h:31
grpc_core::grpc_lb_priority_trace
TraceFlag grpc_lb_priority_trace(false, "priority_lb")
ExitIdleLocked
void StartUpdate() ABSL_EXCLUSIVE_LOCKS_REQUIRED(&RlsLb void MaybeFinishUpdate() ABSL_LOCKS_EXCLUDED(&RlsLb void ExitIdleLocked()
Definition: rls.cc:299
absl::string_view
Definition: abseil-cpp/absl/strings/string_view.h:167
testing::internal::string
::std::string string
Definition: bloaty/third_party/protobuf/third_party/googletest/googletest/include/gtest/internal/gtest-port.h:881
error
grpc_error_handle error
Definition: retry_filter.cc:499
lb_policy.h
child_failover_timeout_
const Duration child_failover_timeout_
Definition: priority.cc:282
timer_pending_
bool timer_pending_
Definition: priority.cc:205
addresses_
absl::StatusOr< HierarchicalAddressMap > addresses_
Definition: priority.cc:287
lb_policy_factory.h
GRPC_CHANNEL_TRANSIENT_FAILURE
@ GRPC_CHANNEL_TRANSIENT_FAILURE
Definition: include/grpc/impl/codegen/connectivity_state.h:38
UINT32_MAX
#define UINT32_MAX
Definition: stdint-msvc2008.h:142
closure.h
status
absl::Status status
Definition: rls.cc:251
setup.name
name
Definition: setup.py:542
xds_manager.p
p
Definition: xds_manager.py:60
name_
const std::string name_
Definition: priority.cc:233
grpc_timer
Definition: iomgr/timer.h:33
grpc_pollset_set_del_pollset_set
void grpc_pollset_set_del_pollset_set(grpc_pollset_set *bag, grpc_pollset_set *item)
Definition: pollset_set.cc:52
seen_ready_or_idle_since_transient_failure_
bool seen_ready_or_idle_since_transient_failure_
Definition: priority.cc:242
grpc_channel_args
Definition: grpc_types.h:132
GRPC_TRACE_FLAG_ENABLED
#define GRPC_TRACE_FLAG_ENABLED(f)
Definition: debug/trace.h:114
subchannel_interface.h
grpc_core::MakeHierarchicalAddressMap
absl::StatusOr< HierarchicalAddressMap > MakeHierarchicalAddressMap(const absl::StatusOr< ServerAddressList > &addresses)
Definition: address_filtering.cc:76
message
char * message
Definition: libuv/docs/code/tty-gravity/main.c:12
grpc_connectivity_state
grpc_connectivity_state
Definition: include/grpc/impl/codegen/connectivity_state.h:30
GRPC_ERROR_CREATE_FROM_VECTOR
#define GRPC_ERROR_CREATE_FROM_VECTOR(desc, error_list)
Definition: error.h:314
grpc_types.h
absl::synchronization_internal::Get
static GraphId Get(const IdMap &id, int num)
Definition: abseil-cpp/absl/synchronization/internal/graphcycles_test.cc:44
uint32_t
unsigned int uint32_t
Definition: stdint-msvc2008.h:80
config
RefCountedPtr< LoadBalancingPolicy::Config > config
Definition: priority.cc:84
DEBUG_LOCATION
#define DEBUG_LOCATION
Definition: debug_location.h:41
absl::Milliseconds
constexpr Duration Milliseconds(T n)
Definition: third_party/abseil-cpp/absl/time/time.h:415
grpc_lb_policy_priority_shutdown
void grpc_lb_policy_priority_shutdown()
Definition: priority.cc:1058
priority_policy_
RefCountedPtr< PriorityLb > priority_policy_
Definition: priority.cc:232
asyncio_get_stats.args
args
Definition: asyncio_get_stats.py:40
absl::move
constexpr absl::remove_reference_t< T > && move(T &&t) noexcept
Definition: abseil-cpp/absl/utility/utility.h:221
GPR_ASSERT
#define GPR_ASSERT(x)
Definition: include/grpc/impl/codegen/log.h:94
hpack_encoder_fixtures::Args
Args({0, 16384})
array
Definition: undname.c:101
priorities_
const std::vector< std::string > priorities_
Definition: priority.cc:101
gen_stats_data.c_str
def c_str(s, encoding='ascii')
Definition: gen_stats_data.py:38
Json
JSON (JavaScript Object Notation).
Definition: third_party/bloaty/third_party/protobuf/conformance/third_party/jsoncpp/json.h:227
connectivity_status_
absl::Status connectivity_status_
Definition: priority.cc:239
grpc_lb_policy_priority_init
void grpc_lb_policy_priority_init()
Definition: priority.cc:1052
gpr_log
GPRAPI void gpr_log(const char *file, int line, gpr_log_severity severity, const char *format,...) GPR_PRINT_FORMAT_CHECK(4
deactivation_timer_
OrphanablePtr< DeactivationTimer > deactivation_timer_
Definition: priority.cc:244
work_serializer.h
connectivity_state.h
pollset_set.h
GRPC_CHANNEL_IDLE
@ GRPC_CHANNEL_IDLE
Definition: include/grpc/impl/codegen/connectivity_state.h:32
grpc_channel_args_destroy
void grpc_channel_args_destroy(grpc_channel_args *a)
Definition: channel_args.cc:360
arg
Definition: cmdline.cc:40
server_address.h
time.h
grpc_channel_args_copy
grpc_channel_args * grpc_channel_args_copy(const grpc_channel_args *src)
Definition: channel_args.cc:285
googletest-filter-unittest.child
child
Definition: bloaty/third_party/googletest/googletest/test/googletest-filter-unittest.py:62
child_priority_
RefCountedPtr< ChildPriority > child_priority_
Definition: priority.cc:202
error.h
connectivity_state_
grpc_connectivity_state connectivity_state_
Definition: priority.cc:238
json.h
on_timer_
grpc_closure on_timer_
Definition: priority.cc:204
GRPC_ERROR_CREATE_FROM_STATIC_STRING
#define GRPC_ERROR_CREATE_FROM_STATIC_STRING(desc)
Definition: error.h:291
current_child_from_before_update_
ChildPriority * current_child_from_before_update_
Definition: priority.cc:302
GRPC_CHANNEL_CONNECTING
@ GRPC_CHANNEL_CONNECTING
Definition: include/grpc/impl/codegen/connectivity_state.h:34
timer_
grpc_timer timer_
Definition: priority.cc:203
child_policy_handler.h
picker_
std::unique_ptr< SubchannelPicker > picker_
Definition: priority.cc:157
child_policy_
OrphanablePtr< LoadBalancingPolicy > child_policy_
Definition: priority.cc:236
grpc_timer_cancel
void grpc_timer_cancel(grpc_timer *timer)
Definition: iomgr/timer.cc:36
absl::Now
ABSL_NAMESPACE_BEGIN Time Now()
Definition: abseil-cpp/absl/time/clock.cc:39
grpc_core::ConnectivityStateName
const char * ConnectivityStateName(grpc_connectivity_state state)
Definition: connectivity_state.cc:38
GRPC_ERROR_REF
#define GRPC_ERROR_REF(err)
Definition: error.h:261
debug_location.h
config_
RefCountedPtr< PriorityLbConfig > config_
Definition: priority.cc:286
lb_policy_registry.h
GRPC_ERROR_CREATE_REFERENCING_FROM_COPIED_STRING
#define GRPC_ERROR_CREATE_REFERENCING_FROM_COPIED_STRING(desc, errs, count)
Definition: error.h:310
ref_counted.h
absl::Status
Definition: third_party/abseil-cpp/absl/status/status.h:424
GRPC_ERROR_CREATE_FROM_CPP_STRING
#define GRPC_ERROR_CREATE_FROM_CPP_STRING(desc)
Definition: error.h:297
children_
const std::map< std::string, PriorityLbChild > children_
Definition: priority.cc:100
std
Definition: grpcpp/impl/codegen/async_unary_call.h:407
grpc_pollset_set_add_pollset_set
void grpc_pollset_set_add_pollset_set(grpc_pollset_set *bag, grpc_pollset_set *item)
Definition: pollset_set.cc:47
grpc_core::Duration::Seconds
static constexpr Duration Seconds(int64_t seconds)
Definition: src/core/lib/gprpp/time.h:151
GRPC_ARG_PRIORITY_FAILOVER_TIMEOUT_MS
#define GRPC_ARG_PRIORITY_FAILOVER_TIMEOUT_MS
Definition: grpc_types.h:374
arg
struct arg arg
state
Definition: bloaty/third_party/zlib/contrib/blast/blast.c:41
exec_ctx.h
grpc_timer_init
void grpc_timer_init(grpc_timer *timer, grpc_core::Timestamp deadline, grpc_closure *closure)
Definition: iomgr/timer.cc:31
absl::UnavailableError
Status UnavailableError(absl::string_view message)
Definition: third_party/abseil-cpp/absl/status/status.cc:375
GRPC_ERROR_UNREF
#define GRPC_ERROR_UNREF(err)
Definition: error.h:262
shutting_down_
bool shutting_down_
Definition: priority.cc:290
ref_counted_ptr.h
config_s
Definition: bloaty/third_party/zlib/deflate.c:120
ignore_reresolution_requests
bool ignore_reresolution_requests
Definition: priority.cc:85
channel_args.h
timer.h
update_in_progress_
bool update_in_progress_
Definition: priority.cc:292
check_redundant_namespace_qualifiers.Config
Config
Definition: check_redundant_namespace_qualifiers.py:142
Duration
Definition: bloaty/third_party/protobuf/src/google/protobuf/duration.pb.h:69
absl::StatusOr
Definition: abseil-cpp/absl/status/statusor.h:187
absl::StatusCode::kUnavailable
@ kUnavailable
picker_wrapper_
RefCountedPtr< RefCountedPicker > picker_wrapper_
Definition: priority.cc:240
grpc_error
Definition: error_internal.h:42
size
voidpf void uLong size
Definition: bloaty/third_party/zlib/contrib/minizip/ioapi.h:136
args_
const grpc_channel_args * args_
Definition: priority.cc:285
grpc_channel_args_find_integer
int grpc_channel_args_find_integer(const grpc_channel_args *args, const char *name, const grpc_integer_options options)
Definition: channel_args.cc:425
testing::Ref
internal::RefMatcher< T & > Ref(T &x)
Definition: cares/cares/test/gmock-1.8.0/gmock/gmock.h:8628
grpc_closure
Definition: closure.h:56
current_priority_
uint32_t current_priority_
Definition: priority.cc:298
children
std::map< std::string, Node * > children
Definition: bloaty/third_party/protobuf/src/google/protobuf/util/field_mask_util.cc:257
i
uint64_t i
Definition: abseil-cpp/absl/container/btree_benchmark.cc:230
state
static struct rpc_state state
Definition: bad_server_response_test.cc:87
GRPC_ERROR_IS_NONE
#define GRPC_ERROR_IS_NONE(err)
Definition: error.h:241
parse_error
@ parse_error
Definition: pem_info.c:88
grpc_core::Duration::Minutes
static constexpr Duration Minutes(int64_t minutes)
Definition: src/core/lib/gprpp/time.h:147
port_platform.h


grpc
Author(s):
autogenerated on Thu Mar 13 2025 03:00:55