summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/SharedThreadPool.cpp
blob: a2de1c44959e8a9e56e500a670f596d7e44603da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/SharedThreadPool.h"
#include "mozilla/Monitor.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "nsTHashMap.h"
#include "nsXPCOMCIDInternal.h"
#include "nsComponentManagerUtils.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIThreadManager.h"
#include "nsThreadPool.h"

namespace mozilla {

// Created and destroyed on the main thread.
static StaticAutoPtr<ReentrantMonitor> sMonitor;

// Hashtable, maps thread pool name to SharedThreadPool instance.
// Modified only on the main thread.
static StaticAutoPtr<nsTHashMap<nsCStringHashKey, SharedThreadPool*>> sPools;

static already_AddRefed<nsIThreadPool> CreateThreadPool(const nsCString& aName);

class SharedThreadPoolShutdownObserver : public nsIObserver {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
 protected:
  virtual ~SharedThreadPoolShutdownObserver() = default;
};

NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports)

NS_IMETHODIMP
SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject,
                                          const char* aTopic,
                                          const char16_t* aData) {
  MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads"));
#ifdef EARLY_BETA_OR_EARLIER
  {
    ReentrantMonitorAutoEnter mon(*sMonitor);
    if (!sPools->IsEmpty()) {
      nsAutoCString str;
      for (const auto& key : sPools->Keys()) {
        str.AppendPrintf("\"%s\" ", nsAutoCString(key).get());
      }
      printf_stderr(
          "SharedThreadPool in xpcom-shutdown-threads. Waiting for "
          "pools %s\n",
          str.get());
    }
  }
#endif
  SharedThreadPool::SpinUntilEmpty();
  sMonitor = nullptr;
  sPools = nullptr;
  return NS_OK;
}

void SharedThreadPool::InitStatics() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sMonitor && !sPools);
  sMonitor = new ReentrantMonitor("SharedThreadPool");
  sPools = new nsTHashMap<nsCStringHashKey, SharedThreadPool*>();
  nsCOMPtr<nsIObserverService> obsService =
      mozilla::services::GetObserverService();
  nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver();
  obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
}

/* static */
bool SharedThreadPool::IsEmpty() {
  ReentrantMonitorAutoEnter mon(*sMonitor);
  return !sPools->Count();
}

/* static */
void SharedThreadPool::SpinUntilEmpty() {
  MOZ_ASSERT(NS_IsMainThread());
  SpinEventLoopUntil("SharedThreadPool::SpinUntilEmpty"_ns, []() -> bool {
    sMonitor->AssertNotCurrentThreadIn();
    return IsEmpty();
  });
}

already_AddRefed<SharedThreadPool> SharedThreadPool::Get(
    const nsCString& aName, uint32_t aThreadLimit) {
  MOZ_ASSERT(sMonitor && sPools);
  ReentrantMonitorAutoEnter mon(*sMonitor);
  RefPtr<SharedThreadPool> pool;

  return sPools->WithEntryHandle(
      aName, [&](auto&& entry) -> already_AddRefed<SharedThreadPool> {
        if (entry) {
          pool = entry.Data();
          if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) {
            NS_WARNING("Failed to set limits on thread pool");
          }
        } else {
          nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName));
          if (NS_WARN_IF(!threadPool)) {
            sPools->Remove(aName);  // XXX entry.Remove()
            return nullptr;
          }
          pool = new SharedThreadPool(aName, threadPool);

          // Set the thread and idle limits. Note that we don't rely on the
          // EnsureThreadLimitIsAtLeast() call below, as the default thread
          // limit is 4, and if aThreadLimit is less than 4 we'll end up with a
          // pool with 4 threads rather than what we expected; so we'll have
          // unexpected behaviour.
          nsresult rv = pool->SetThreadLimit(aThreadLimit);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            sPools->Remove(aName);  // XXX entry.Remove()
            return nullptr;
          }

          rv = pool->SetIdleThreadLimit(aThreadLimit);
          if (NS_WARN_IF(NS_FAILED(rv))) {
            sPools->Remove(aName);  // XXX entry.Remove()
            return nullptr;
          }

          entry.Insert(pool.get());
        }

        return pool.forget();
      });
}

NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) {
  MOZ_ASSERT(sMonitor);
  ReentrantMonitorAutoEnter mon(*sMonitor);
  MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
  nsrefcnt count = ++mRefCnt;
  NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this));
  return count;
}

NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) {
  MOZ_ASSERT(sMonitor);
  ReentrantMonitorAutoEnter mon(*sMonitor);
  nsrefcnt count = --mRefCnt;
  NS_LOG_RELEASE(this, count, "SharedThreadPool");
  if (count) {
    return count;
  }

  // Remove SharedThreadPool from table of pools.
  sPools->Remove(mName);
  MOZ_ASSERT(!sPools->Get(mName));

  // Dispatch an event to the main thread to call Shutdown() on
  // the nsIThreadPool. The Runnable here will add a refcount to the pool,
  // and when the Runnable releases the nsIThreadPool it will be deleted.
  NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool,
                                            &nsIThreadPool::Shutdown));

  // Stabilize refcount, so that if something in the dtor QIs, it won't explode.
  mRefCnt = 1;
  delete this;
  return 0;
}

NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget)

SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool)
    : mName(aName), mPool(aPool), mRefCnt(0) {}

SharedThreadPool::~SharedThreadPool() = default;

nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) {
  // We limit the number of threads that we use. Note that we
  // set the thread limit to the same as the idle limit so that we're not
  // constantly creating and destroying threads (see Bug 881954). When the
  // thread pool threads shutdown they dispatch an event to the main thread
  // to call nsIThread::Shutdown(), and if we're very busy that can take a
  // while to run, and we end up with dozens of extra threads. Note that
  // threads that are idle for 60 seconds are shutdown naturally.
  uint32_t existingLimit = 0;
  nsresult rv;

  rv = mPool->GetThreadLimit(&existingLimit);
  NS_ENSURE_SUCCESS(rv, rv);
  if (aLimit > existingLimit) {
    rv = mPool->SetThreadLimit(aLimit);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = mPool->GetIdleThreadLimit(&existingLimit);
  NS_ENSURE_SUCCESS(rv, rv);
  if (aLimit > existingLimit) {
    rv = mPool->SetIdleThreadLimit(aLimit);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

static already_AddRefed<nsIThreadPool> CreateThreadPool(
    const nsCString& aName) {
  nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();

  nsresult rv = pool->SetName(aName);
  NS_ENSURE_SUCCESS(rv, nullptr);

  rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
  NS_ENSURE_SUCCESS(rv, nullptr);

  return pool.forget();
}

}  // namespace mozilla