1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "Shutdown.h"
6 #include "mozilla/Unused.h"
7 
8 namespace mozilla {
9 namespace places {
10 
11 uint16_t PlacesShutdownBlocker::sCounter = 0;
12 Atomic<bool> PlacesShutdownBlocker::sIsStarted(false);
13 
PlacesShutdownBlocker(const nsString & aName)14 PlacesShutdownBlocker::PlacesShutdownBlocker(const nsString& aName)
15   : mName(aName)
16   , mState(NOT_STARTED)
17   , mCounter(sCounter++)
18 {
19   MOZ_ASSERT(NS_IsMainThread());
20   // During tests, we can end up with the Database singleton being resurrected.
21   // Make sure that each instance of DatabaseShutdown has a unique name.
22   if (mCounter > 1) {
23     mName.AppendInt(mCounter);
24   }
25 }
26 
27 // nsIAsyncShutdownBlocker
28 NS_IMETHODIMP
GetName(nsAString & aName)29 PlacesShutdownBlocker::GetName(nsAString& aName)
30 {
31   aName = mName;
32   return NS_OK;
33 }
34 
35 // nsIAsyncShutdownBlocker
36 NS_IMETHODIMP
GetState(nsIPropertyBag ** _state)37 PlacesShutdownBlocker::GetState(nsIPropertyBag** _state)
38 {
39   NS_ENSURE_ARG_POINTER(_state);
40 
41   nsCOMPtr<nsIWritablePropertyBag2> bag =
42     do_CreateInstance("@mozilla.org/hash-property-bag;1");
43   NS_ENSURE_TRUE(bag, NS_ERROR_OUT_OF_MEMORY);
44   bag.forget(_state);
45 
46   // Put `mState` in field `progress`
47   RefPtr<nsVariant> progress = new nsVariant();
48   nsresult rv = progress->SetAsUint8(mState);
49   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
50   rv = static_cast<nsIWritablePropertyBag2*>(*_state)->SetPropertyAsInterface(
51     NS_LITERAL_STRING("progress"), progress);
52   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
53 
54   // Put `mBarrier`'s state in field `barrier`, if possible
55   if (!mBarrier) {
56     return NS_OK;
57   }
58   nsCOMPtr<nsIPropertyBag> barrierState;
59   rv = mBarrier->GetState(getter_AddRefs(barrierState));
60   if (NS_FAILED(rv)) {
61     return NS_OK;
62   }
63 
64   RefPtr<nsVariant> barrier = new nsVariant();
65   rv = barrier->SetAsInterface(NS_GET_IID(nsIPropertyBag), barrierState);
66   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
67   rv = static_cast<nsIWritablePropertyBag2*>(*_state)->SetPropertyAsInterface(
68     NS_LITERAL_STRING("Barrier"), barrier);
69   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
70 
71   return NS_OK;
72 }
73 
74 // nsIAsyncShutdownBlocker
75 NS_IMETHODIMP
BlockShutdown(nsIAsyncShutdownClient * aParentClient)76 PlacesShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
77 {
78   MOZ_ASSERT(false, "should always be overridden");
79   return NS_ERROR_NOT_IMPLEMENTED;
80 }
81 
NS_IMPL_ISUPPORTS(PlacesShutdownBlocker,nsIAsyncShutdownBlocker)82 NS_IMPL_ISUPPORTS(
83   PlacesShutdownBlocker,
84   nsIAsyncShutdownBlocker
85 )
86 
87 ////////////////////////////////////////////////////////////////////////////////
88 
89 ClientsShutdownBlocker::ClientsShutdownBlocker()
90   : PlacesShutdownBlocker(NS_LITERAL_STRING("Places Clients shutdown"))
91 {
92   MOZ_ASSERT(NS_IsMainThread());
93   // Create a barrier that will be exposed to clients through GetClient(), so
94   // they can block Places shutdown.
95   nsCOMPtr<nsIAsyncShutdownService> asyncShutdown = services::GetAsyncShutdown();
96   MOZ_ASSERT(asyncShutdown);
97   if (asyncShutdown) {
98     nsCOMPtr<nsIAsyncShutdownBarrier> barrier;
99     MOZ_ALWAYS_SUCCEEDS(asyncShutdown->MakeBarrier(mName, getter_AddRefs(barrier)));
100     mBarrier = new nsMainThreadPtrHolder<nsIAsyncShutdownBarrier>(barrier);
101   }
102 }
103 
104 already_AddRefed<nsIAsyncShutdownClient>
GetClient()105 ClientsShutdownBlocker::GetClient()
106 {
107   nsCOMPtr<nsIAsyncShutdownClient> client;
108   if (mBarrier) {
109     MOZ_ALWAYS_SUCCEEDS(mBarrier->GetClient(getter_AddRefs(client)));
110   }
111   return client.forget();
112 }
113 
114 // nsIAsyncShutdownBlocker
115 NS_IMETHODIMP
BlockShutdown(nsIAsyncShutdownClient * aParentClient)116 ClientsShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
117 {
118   MOZ_ASSERT(NS_IsMainThread());
119   mParentClient = new nsMainThreadPtrHolder<nsIAsyncShutdownClient>(aParentClient);
120   mState = RECEIVED_BLOCK_SHUTDOWN;
121 
122   if (NS_WARN_IF(!mBarrier)) {
123     return NS_ERROR_NOT_AVAILABLE;
124   }
125 
126   // Wait until all the clients have removed their blockers.
127   MOZ_ALWAYS_SUCCEEDS(mBarrier->Wait(this));
128 
129   mState = CALLED_WAIT_CLIENTS;
130   return NS_OK;
131 }
132 
133 // nsIAsyncShutdownCompletionCallback
134 NS_IMETHODIMP
Done()135 ClientsShutdownBlocker::Done()
136 {
137   // At this point all the clients are done, we can stop blocking the shutdown
138   // phase.
139   mState = RECEIVED_DONE;
140 
141   // mParentClient is nullptr in tests.
142   if (mParentClient) {
143     nsresult rv = mParentClient->RemoveBlocker(this);
144     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
145     mParentClient = nullptr;
146   }
147   mBarrier = nullptr;
148   return NS_OK;
149 }
150 
NS_IMPL_ISUPPORTS_INHERITED(ClientsShutdownBlocker,PlacesShutdownBlocker,nsIAsyncShutdownCompletionCallback)151 NS_IMPL_ISUPPORTS_INHERITED(
152   ClientsShutdownBlocker,
153   PlacesShutdownBlocker,
154   nsIAsyncShutdownCompletionCallback
155 )
156 
157 ////////////////////////////////////////////////////////////////////////////////
158 
159 ConnectionShutdownBlocker::ConnectionShutdownBlocker(Database* aDatabase)
160   : PlacesShutdownBlocker(NS_LITERAL_STRING("Places Connection shutdown"))
161   , mDatabase(aDatabase)
162 {
163   // Do nothing.
164 }
165 
166 // nsIAsyncShutdownBlocker
167 NS_IMETHODIMP
BlockShutdown(nsIAsyncShutdownClient * aParentClient)168 ConnectionShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
169 {
170   MOZ_ASSERT(NS_IsMainThread());
171   mParentClient = new nsMainThreadPtrHolder<nsIAsyncShutdownClient>(aParentClient);
172   mState = RECEIVED_BLOCK_SHUTDOWN;
173   // Annotate that Database shutdown started.
174   sIsStarted = true;
175 
176   // Fire internal database closing notification.
177   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
178   MOZ_ASSERT(os);
179   if (os) {
180     Unused << os->NotifyObservers(nullptr, TOPIC_PLACES_WILL_CLOSE_CONNECTION, nullptr);
181   }
182   mState = NOTIFIED_OBSERVERS_PLACES_WILL_CLOSE_CONNECTION;
183 
184   // At this stage, any use of this database is forbidden. Get rid of
185   // `gDatabase`. Note, however, that the database could be
186   // resurrected.  This can happen in particular during tests.
187   MOZ_ASSERT(Database::gDatabase == nullptr || Database::gDatabase == mDatabase);
188   Database::gDatabase = nullptr;
189 
190   // Database::Shutdown will invoke Complete once the connection is closed.
191   mDatabase->Shutdown();
192   mState = CALLED_STORAGESHUTDOWN;
193   return NS_OK;
194 }
195 
196 // mozIStorageCompletionCallback
197 NS_IMETHODIMP
Complete(nsresult,nsISupports *)198 ConnectionShutdownBlocker::Complete(nsresult, nsISupports*)
199 {
200   MOZ_ASSERT(NS_IsMainThread());
201   mState = RECEIVED_STORAGESHUTDOWN_COMPLETE;
202 
203   // The connection is closed, the Database has no more use, so we can break
204   // possible cycles.
205   mDatabase = nullptr;
206 
207   // Notify the connection has gone.
208   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
209   MOZ_ASSERT(os);
210   if (os) {
211     MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr,
212 					    TOPIC_PLACES_CONNECTION_CLOSED,
213 					    nullptr));
214   }
215   mState = NOTIFIED_OBSERVERS_PLACES_CONNECTION_CLOSED;
216 
217   // mParentClient is nullptr in tests
218   if (mParentClient) {
219     nsresult rv = mParentClient->RemoveBlocker(this);
220     if (NS_WARN_IF(NS_FAILED(rv))) return rv;
221     mParentClient = nullptr;
222   }
223   return NS_OK;
224 }
225 
226 NS_IMPL_ISUPPORTS_INHERITED(
227   ConnectionShutdownBlocker,
228   PlacesShutdownBlocker,
229   mozIStorageCompletionCallback
230 )
231 
232 } // namespace places
233 } // namespace mozilla
234