-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stop search indexer when it is deleted. #16
base: master
Are you sure you want to change the base?
Conversation
src/dreyfus_index_manager.erl
Outdated
DbShards = [mem3:name(Sh) || Sh <- mem3:local_shards(mem3:dbname(DbName))], | ||
lists:foreach(fun(DbShard) -> | ||
lists:foreach(fun({_DbShard, {_DDocId, Sig}}) -> | ||
case ets:lookup(?BY_SIG, {DbShard, Sig}) of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indent case
src/dreyfus_index_manager.erl
Outdated
[] -> | ||
ok | ||
end | ||
end, ets:match_object(?BY_DB, {DbShard, {DDocId, '$1'}})) end, DbShards), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you move second end
to another line?
src/dreyfus_index_manager.erl
Outdated
@@ -113,25 +115,48 @@ handle_db_event(DbName, created, _St) -> | |||
handle_db_event(DbName, deleted, _St) -> | |||
gen_server:cast(?MODULE, {cleanup, DbName}), | |||
{ok, nil}; | |||
handle_db_event(DbName, {ddoc_updated, DDocId}, _St) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are subscribing for all_dbs
here. This means that you would get non-clustered databases as well. You should distinguish these two cases. Which can be done by matching on <<"shards/", _/binary>> = DbName
.
ok = config:set("admins", ?USER, ?PASS, _Persist=false), | ||
Ctx. | ||
|
||
setup(PortType) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dreyfus is not accessible on backdoor interface. Therefore there is no need to pass PortType and use foreachx
in the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to update this unit test without the PortType and couldn't make it work.. Can you please show me an example unit test code that I can follow? I want to use this in the mean time as it's doing the intended job.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is one example of it https://github.com/apache/couchdb/blob/master/src/couch/test/couchdb_compaction_daemon_tests.erl#L62
dreyfus_by_db, {'$1', {DDocId, Sig}}), | ||
lists:foldl(fun({DbName, {_DDocId, _Sig}}, Acc) -> | ||
case ets:lookup(dreyfus_by_sig, {DbName, Sig}) of | ||
[{_, Pid}] -> [Pid| Acc]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no guarantee that dreyfus_by_sig
ets table contains Pid of an indexer process. There is a case when it would contain a Pid of an opener process see here.
In order to resolve this ambiguity you could use the approach similar to the this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, I am issuing a search request, before I call this method.. so it would have opened the index pid before it executes this code..
]}, | ||
UpdatedDDocJsonObj. | ||
|
||
ensureIndexIsOpened(DbName, DDocId, IndexName) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't use camel case naming convention in erlang
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
test_request:get(ReqUrl, [?AUTH]), | ||
?assertEqual(200, Status), | ||
DDocId = "_design/searchddoc", | ||
Sig = ensureIndexIsOpened(DbName, DDocId, "other"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You want to make sure that the following conditions are satisfied:
- the deleted index is not in ets table
- the old index Pid is not running after deletion of search index.
Also keep in mind that you would have multiple indexers running (one per database shard).
I would suggest to use Mayya's approach from here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the code in here is taking care of handling the event in all db shards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if the index pid is not there in ets means it is not running right? Isn't that enough to verify that the index pid for the old index is not running? What's the scenario where the index pid would not be in ets but would still be running.. I don't think that's possible..
}. | ||
|
||
should_stop_indexing_after_deleting_index(PortType, {Host, DbName}) -> | ||
{timeout, 60, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why you had to increase timeout?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the test was failing if I didn't increase timeout.. May be because I am waiting for the index to be closed before I verify the index is indeed closed.. And that wait seems like is causing the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some comments
Thanks @iilyak. Will check them out and make the suggested changes as needed. |
0739e23
to
7214672
Compare
src/dreyfus_index.erl
Outdated
@@ -40,6 +40,8 @@ | |||
waiting_list=[] | |||
}). | |||
|
|||
-define(INDEX_SHUTDOWN_DELAY, 60000). % 1 minute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to be configurable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like we don't have any config for dreyfus, if I make this configurable then do we need to add a new config file?
Not sure if it's worth the effort to make this configurable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean config:get
, that's available to all applications.
src/dreyfus_index.erl
Outdated
false | ||
end | ||
end, | ||
case IndexDeleted of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indentation is misleadingly wrong here.
src/dreyfus_index.erl
Outdated
@@ -157,9 +159,43 @@ handle_call(info, _From, State) -> % obsolete | |||
Reply = info_int(State#state.index_pid), | |||
{reply, Reply, State}. | |||
|
|||
handle_cast({ddoc_updated, DDocResult}, State) -> | |||
#state{dbname = DbName, index=#index{sig=CurrentSig}, waiting_list = WaitList} = State, | |||
IndexDeleted = case DDocResult of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract this into a function for clarity.
src/dreyfus_index.erl
Outdated
case Index of | ||
[] -> | ||
true; | ||
_ -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
match something explicitly here, a non-empty list, for example. we don't expect any other result from get_index_with_sig, so don't have clauses for them.
src/dreyfus_index.erl
Outdated
end; | ||
false -> | ||
{noreply, State} | ||
end; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we always return {noreply, State}, move it to after the case statement so we say it once.
src/dreyfus_index.erl
Outdated
lists:flatmap( | ||
fun(IndexName) -> | ||
case (catch design_doc_to_index(Doc, IndexName)) of | ||
{ok, #index{sig=Sig_New}=Index} when Sig_New =:= Sig -> [Index]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could just do {ok, #index{sig=Sig}=Index}
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to compare the if the index list has the index matching the Sig that was passed in as a parameter to this function.
src/dreyfus_index_manager.erl
Outdated
lists:foreach(fun({_DbShard, {_DDocId, Sig}}) -> | ||
case ets:lookup(?BY_SIG, {DbShard, Sig}) of | ||
[{_, IndexPid}] -> | ||
(catch gen_server:cast(IndexPid, {ddoc_updated, DDocResult})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why catch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I just copied the code from couch_index_server and hence the catch.. I thought the motivation was to not kill the index manager if there was an error sending message to search index.. But only time this could raise an error is when something really bad happens to the node.. I will take out the exception..
src/dreyfus_index.erl
Outdated
{IndexNames, _} = lists:unzip(IndexList), | ||
lists:flatmap( | ||
fun(IndexName) -> | ||
case (catch design_doc_to_index(Doc, IndexName)) of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why catch?
src/dreyfus_index_manager.erl
Outdated
[] -> | ||
ok | ||
end | ||
end, ets:match_object(?BY_DB, {DbShard, {DDocId, '$1'}})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the $1 for if we don't apply a match condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed the same concept as in couch_index_server. I thought this would basically find all the search indices opened for the given ddoc matching DDocId. Am I missing something here?
src/dreyfus_index_manager.erl
Outdated
|
||
delete_from_ets(Pid, DbName, Sig) -> | ||
case ets:match_object(?BY_DB, {DbName, {'$1', Sig}}) of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the $1 for if we don't apply a match condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to find the design doc id for a given DB and Search index, so that I can remove it from the dreyfus_by_db ets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Robert mentioned this shouldn't be a '$1'
, but '_'
. We need to bind if we want to compare that the values at different positions are the same. For example {'$1', a, '$1'}
would match {22, a, 22}
, but it wouldn't match {44, a, 32}
.
7214672
to
ddd87b2
Compare
ddd87b2
to
9194218
Compare
9194218
to
7e0d982
Compare
src/dreyfus_index.erl
Outdated
@@ -40,6 +40,9 @@ | |||
waiting_list=[] | |||
}). | |||
|
|||
-define(INDEX_SHUTDOWN_DELAY, | |||
config:get("dreyfus", "index_shutdown_delay", 60000)). % 1 minute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config:get returns a string, so the default needs to be a string, or use get_integer. And move this to a function please.
7e0d982
to
4abddf9
Compare
@rnewson, Done. Moved the config:get to a function and also changed it to use get_integer. |
?assertEqual(0, length(IndexPidsFromEts)), | ||
ok. | ||
|
||
getIndex(DbName, DDocId, IndexName) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't use CamelCase naming in function names. this function would be called get_index
.
4abddf9
to
c7e1278
Compare
?assertEqual(0, length(IndexPidsFromEts)), | ||
ok. | ||
|
||
getIndex(DbName, DDocId, IndexName) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we return not_found
instead of ok
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really.. The intent of that function is that it ensures that the index is closed and returning 'ok only if it succeeds that.. otherwise the assert would fail and cause the test to stop..
?assert(length(IndexPidsFromEts) > 0), | ||
Sig. | ||
|
||
ensure_index_is_closed(DbName, DDocId, IndexName, Sig) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you know that index Pids are actually killed? It seems like you rely on the info from ets. In this case if we would introduce a bug in a Pid accounting in ets tables we wouldn't be able to catch the problem.
You could pass the Pids of indexer instances as an argument to this function or make this check in the test case itself.
|
||
updated_ddoc_json_obj(DbName, DDocId) -> | ||
{ok, DDoc} = fabric:open_doc(DbName, <<DDocId/binary>>, [ejson_body, ?ADMIN_CTX]), | ||
% {DDocJsonObj} = ?DDOC_UPDATE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to remove commented out code in the final version.
c7e1278
to
2db1f44
Compare
@iilyak Changed the test code to pass the indexpids and ensure that they are closed. |
2db1f44
to
ca5ab30
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some substantial issues to address here.
src/dreyfus_index.erl
Outdated
@@ -40,6 +40,8 @@ | |||
waiting_list=[] | |||
}). | |||
|
|||
-define(INDEX_SHUTDOWN_DELAY, index_shutdown_delay()). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this define, just call the function where needed.
src/dreyfus_index.erl
Outdated
handle_cast({ddoc_updated, DDocResult}, State) -> | ||
#state{index=#index{sig=CurrentSig}, waiting_list = WaitList} = State, | ||
case check_if_index_is_deleted(DDocResult, CurrentSig) of | ||
true -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this case statement will crash when the index isn't deleted as you haven't written the false
case. I would also suggest collapsing this to one case statement using {check_if_index_is_deleted(DDocResult, CurrentSig), WaitList}
src/dreyfus_index.erl
Outdated
case WaitList of | ||
[] -> | ||
self() ! index_deleted; | ||
[_Index] -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this case statement assumes the wait list will have zero or one items, and crashes if there is more than one waiter.
src/dreyfus_index.erl
Outdated
[] -> | ||
self() ! index_deleted; | ||
[_Index] -> | ||
erlang:send_after(?INDEX_SHUTDOWN_DELAY, self(), index_deleted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be better to wait until the list is empty. New requests should not be possible if the index has been deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the whole idea of this work is to stop indexing for the deleted indices which could have already triggered and in progress. Agree that there won't be any _search end point requests after the index is deleted. Basically giving 1 minute to complete the pending requests, if they are not done then it will end the request.
src/dreyfus_index.erl
Outdated
get_index_with_sig(#doc{body={Fields}}=Doc, Sig) -> | ||
RawIndexes = couch_util:get_value(<<"indexes">>, Fields, {[]}), | ||
case RawIndexes of | ||
{IndexList} when is_list(IndexList) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this case statement has one clause, and crashes on anything else. If that's the intent, you don't need a case statement.
src/dreyfus_index.erl
Outdated
@@ -222,6 +243,35 @@ code_change(_OldVsn, State, _Extra) -> | |||
|
|||
% private functions. | |||
|
|||
get_index_with_sig(#doc{body={Fields}}=Doc, Sig) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function only looks for an index of given signature in a single design document. A search index might be referred to by multiple design documents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know about this case of index could be referred by multiple design documents. If that's the case I have to scan all the design docs in the db and ensure that it's not referred in other docs.
src/dreyfus_index_manager.erl
Outdated
[] -> | ||
ok | ||
end | ||
end, ets:match_object(?BY_DB, {DbShard, {DDocId, '$1'}})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace '$1'
with '_'
|
||
get_index_by_sig_from_ets(DDocId, Sig) -> | ||
Indexes = ets:match_object( | ||
dreyfus_by_db, {'$1', {DDocId, Sig}}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replace '$1'
with '_'
ca5ab30
to
fd8e486
Compare
src/dreyfus_index.erl
Outdated
ok; | ||
_ -> | ||
ok | ||
end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part looks good to me
fd8e486
to
b5ce340
Compare
src/dreyfus_index.erl
Outdated
}=State) -> | ||
case check_if_index_is_deleted(DbName, DDocResult, Sig) of | ||
true -> | ||
erlang:send_after(index_shutdown_delay(), self(), index_deleted); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will crash the gen server as it returns a value that handle_cast does not expect (a TimerRef).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the delay for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the gen server should shut down after serving last request after deletion.
src/dreyfus_index.erl
Outdated
case Reason of | ||
{shutdown, ddoc_updated} -> | ||
send_all(State#state.waiting_list, ddoc_updated), | ||
ok; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the return value of terminate is not relevant, I would leave this ok; out.
src/dreyfus_index.erl
Outdated
{not_found, deleted} -> | ||
IndicesWithSig = indices_with_sig_from_other_ddocs(DbName, CurrentSig); | ||
{ok, DDoc} -> | ||
IndicesWithSig = indices_with_sig(DbName, DDoc, CurrentSig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is wrong. we always need to check if any ddoc refers to the index before we can consider that it's deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are indeed doing that.. Inside the indices_with_sig, it will first check if it's deleted then it will check if it referred from other ddocs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per the discussion in slack, will rename/reorg this code to make it easy to read..
b5ce340
to
aec2784
Compare
check_if_index_is_deleted(DbName, DDocResult, CurrentSig) -> | ||
case DDocResult of | ||
{not_found, deleted} -> | ||
IndicesWithSig = indices_with_sig_from_other_ddocs(DbName, CurrentSig); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this indices_with_sig_from_other_ddocs
seems to be very expensive, especially doing exactly same operation for all N shards.
I would suggest avoid this check. First, it is expensive. Second, it seems to me that it is not very likely that the customers have exact same indexes for the same db from diff Design docs and using these Design Docs at the same time. Third, even if we close the index used by another ddoc the danger is minimal, if will be reopened with the next query/ken job.
Even if we still want to keep this check it should be initiated from a single process spawn from dreyfus_index_manager
and then results passed to all concerning dreyfus_index
processes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brkolla Paul D. has commented on my similar code, and it seems that I was wrong. It is common practice to have the same DDocs when updating them. So, please keep the code as before checking on indices_with_sig_from_other_ddocs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
K, will leave it as it is then. Thx.
src/dreyfus_index.erl
Outdated
@@ -157,9 +157,27 @@ handle_call(info, _From, State) -> % obsolete | |||
Reply = info_int(State#state.index_pid), | |||
{reply, Reply, State}. | |||
|
|||
handle_cast({ddoc_updated, DDocResult}, | |||
#state{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent by 4 spaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on principle, we only extract terms in the function head if we are matching on them. so pull Sig and DbName out on a separate line. This will also make the function head much smaller and it will fit on one line.
handle_cast({ddoc_updated, DDocResult}, #state{} = State) ->
#index{sig = Sig, dbname = DbName} = State#state.index,
src/dreyfus_index.erl
Outdated
}=State) -> | ||
case check_if_index_is_deleted(DbName, DDocResult, Sig) of | ||
true -> | ||
erlang:send_after(index_shutdown_delay(), self(), index_deleted), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think I said it before, but an arbitrary delay doesn't seem write. If a design document is updated such that there is no reference to this index, we should either let any requests to it finish and then delete the index or just delete it. The arbitrary delay is, I'm guessing, intended to avoid the errors caused by deleting an index while it's being queried? If so, we should at least document that it's a bit hackish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rnewson what is the way to "let any requests to it finish"? How do we know that requests are finished? check if Waiters
== [] will not work for stale requests that don't wait for indexing to catch up.
aec2784
to
10c62ee
Compare
@rnewson. I have updated the changes to make changes as per your suggestions..
|
Allow dreyfus to listen to ddoc_updated events and check if the index is deleted as a result of update to it's design document. If the index is deleted then it will stop the index. If there are any pids waiting after 30 seconds, it will send a message 'ddoc_updates' before it stops the index. BugzId:85718
10c62ee
to
ce30124
Compare
Allow dreyfus to listen to ddoc_updated events and check if the index
is deleted as a result of update to it's design document. If the index
is deleted then it will stop the index if waitlist is empty, if the
waitlist is not empty then it will wait for a minute before shutting
down.
BugzId:85718