/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "scoped_thread_priority_change.h"

#include <sys/resource.h>

#include "base/mutex.h"
#include "com_android_libcore.h"
#include "thread-inl.h"

namespace art HIDDEN {

void ScopedPriorityChange::SetToNormalOrBetter() {
  static bool enabled = com::android::libcore::niceness_apis();
  if (!enabled) {
    return;
  }
  DCHECK(!priority_changed_);
  int cached_niceness = self_->GetCachedNiceness();
  if (cached_niceness > 0 /* excludes no Java peer case */) {
    int old_niceness = getpriority(PRIO_PROCESS, 0);
    if (old_niceness == cached_niceness) {
      // Reduce nicenesss to zero, increasing our priority.
      // Should succeed on Android; will probably fail elsewhere.
      int ret = setpriority(PRIO_PROCESS, 0, 0);
      if (ret == 0) {
        priority_changed_ = true;
      }
    } else if (old_niceness > 0) {
      // Priority increase is warranted, but cache is out of sync.
      LOG(WARNING) << "Cached niceness = " << cached_niceness
                   << "; actual niceness = " << old_niceness;
    }
  }
}

void ScopedPriorityChange::ResetInternal() {
  DCHECK(priority_changed_);
  DCHECK(com::android::libcore::niceness_apis());
  Locks::mutator_lock_->AssertSharedHeld(self_);
  if (getpriority(PRIO_PROCESS, 0) == 0) {
    // We changed priority and nobody visibly altered it in the interim; change it to the current
    // cached value.
    int cached_niceness = self_->GetCachedNiceness();
    int old_cached_niceness;
    // We cannot acquire the Java monitor that normally protects the niceness field here, since that
    // would sometimes require us to suspend. We instead rely on the fact that any updates to the
    // niceness fields while the thread is running are followed by a setpriority() call with the
    // same niceness value.
    do {
      int ret = setpriority(PRIO_PROCESS, 0, cached_niceness);
      // Increasing our niceness should always work, especially since decreasing it did.
      CHECK_EQ(ret, 0) << strerror(errno);
      old_cached_niceness = cached_niceness;
      cached_niceness = self_->GetCachedNiceness();
      // If it changed in the interim, we may have restored an obsolete niceness value, and we
      // need to fix that. Otherwise any later updates will come with following setpriority()
      // calls, which will overwrite ours.
    } while (cached_niceness != old_cached_niceness);
  } else {
    LOG(WARNING) << "Priority change inside ScopedPriorityChange";
    // Do not restore priority, since somebody else changed it in the interim.
  }
}

}  // namespace art
