• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/command_line.h"
6 #include "chrome/browser/chrome_notification_types.h"
7 #include "chrome/browser/devtools/devtools_window.h"
8 #include "chrome/browser/search/search.h"
9 #include "chrome/browser/ui/browser.h"
10 #include "chrome/browser/ui/browser_commands.h"
11 #include "chrome/browser/ui/singleton_tabs.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "chrome/common/url_constants.h"
15 #include "chrome/test/base/in_process_browser_test.h"
16 #include "chrome/test/base/test_switches.h"
17 #include "chrome/test/base/ui_test_utils.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/render_view_host.h"
21 #include "content/public/browser/render_widget_host_iterator.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_observer.h"
24 #include "content/public/test/browser_test_utils.h"
25 
26 using content::RenderViewHost;
27 using content::RenderWidgetHost;
28 using content::WebContents;
29 
30 namespace {
31 
RenderProcessHostCount()32 int RenderProcessHostCount() {
33   content::RenderProcessHost::iterator hosts =
34       content::RenderProcessHost::AllHostsIterator();
35   int count = 0;
36   while (!hosts.IsAtEnd()) {
37     if (hosts.GetCurrentValue()->HasConnection())
38       count++;
39     hosts.Advance();
40   }
41   return count;
42 }
43 
FindFirstDevToolsContents()44 WebContents* FindFirstDevToolsContents() {
45   scoped_ptr<content::RenderWidgetHostIterator> widgets(
46       RenderWidgetHost::GetRenderWidgetHosts());
47   while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
48     if (!widget->GetProcess()->HasConnection())
49       continue;
50     if (!widget->IsRenderView())
51       continue;
52     RenderViewHost* host = RenderViewHost::From(widget);
53     WebContents* contents = WebContents::FromRenderViewHost(host);
54     GURL url = contents->GetURL();
55     if (url.SchemeIs(content::kChromeDevToolsScheme))
56       return contents;
57   }
58   return NULL;
59 }
60 
61 }  // namespace
62 
63 class ChromeRenderProcessHostTest : public InProcessBrowserTest {
64  public:
ChromeRenderProcessHostTest()65   ChromeRenderProcessHostTest() {}
66 
67   // Show a tab, activating the current one if there is one, and wait for
68   // the renderer process to be created or foregrounded, returning the process
69   // handle.
ShowSingletonTab(const GURL & page)70   base::ProcessHandle ShowSingletonTab(const GURL& page) {
71     chrome::ShowSingletonTab(browser(), page);
72     WebContents* wc = browser()->tab_strip_model()->GetActiveWebContents();
73     CHECK(wc->GetURL() == page);
74 
75     WaitForLauncherThread();
76     WaitForMessageProcessing(wc);
77     return wc->GetRenderProcessHost()->GetHandle();
78   }
79 
80   // Loads the given url in a new background tab and returns the handle of its
81   // renderer.
OpenBackgroundTab(const GURL & page)82   base::ProcessHandle OpenBackgroundTab(const GURL& page) {
83     ui_test_utils::NavigateToURLWithDisposition(browser(), page,
84         NEW_BACKGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
85 
86     TabStripModel* tab_strip = browser()->tab_strip_model();
87     WebContents* wc = tab_strip->GetWebContentsAt(
88         tab_strip->active_index() + 1);
89     CHECK(wc->GetVisibleURL() == page);
90 
91     WaitForLauncherThread();
92     WaitForMessageProcessing(wc);
93     return wc->GetRenderProcessHost()->GetHandle();
94   }
95 
96   // Ensures that the backgrounding / foregrounding gets a chance to run.
WaitForLauncherThread()97   void WaitForLauncherThread() {
98     content::BrowserThread::PostTaskAndReply(
99         content::BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
100         base::Bind(&base::DoNothing), base::MessageLoop::QuitClosure());
101     base::MessageLoop::current()->Run();
102   }
103 
104   // Implicitly waits for the renderer process associated with the specified
105   // WebContents to process outstanding IPC messages by running some JavaScript
106   // and waiting for the result.
WaitForMessageProcessing(WebContents * wc)107   void WaitForMessageProcessing(WebContents* wc) {
108     bool result = false;
109     ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
110         wc, "window.domAutomationController.send(true);", &result));
111     ASSERT_TRUE(result);
112   }
113 
114   // When we hit the max number of renderers, verify that the way we do process
115   // sharing behaves correctly.  In particular, this test is verifying that even
116   // when we hit the max process limit, that renderers of each type will wind up
117   // in a process of that type, even if that means creating a new process.
TestProcessOverflow()118   void TestProcessOverflow() {
119     int tab_count = 1;
120     int host_count = 1;
121     WebContents* tab1 = NULL;
122     WebContents* tab2 = NULL;
123     content::RenderProcessHost* rph1 = NULL;
124     content::RenderProcessHost* rph2 = NULL;
125     content::RenderProcessHost* rph3 = NULL;
126 
127     // Change the first tab to be the omnibox page (TYPE_WEBUI).
128     GURL omnibox(chrome::kChromeUIOmniboxURL);
129     ui_test_utils::NavigateToURL(browser(), omnibox);
130     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
131     tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
132     rph1 = tab1->GetRenderProcessHost();
133     EXPECT_EQ(omnibox, tab1->GetURL());
134     EXPECT_EQ(host_count, RenderProcessHostCount());
135 
136     // Create a new TYPE_TABBED tab.  It should be in its own process.
137     GURL page1("data:text/html,hello world1");
138 
139     ui_test_utils::WindowedTabAddedNotificationObserver observer1(
140         content::NotificationService::AllSources());
141     chrome::ShowSingletonTab(browser(), page1);
142     observer1.Wait();
143 
144     tab_count++;
145     host_count++;
146     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
147     tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
148     rph2 = tab1->GetRenderProcessHost();
149     EXPECT_EQ(tab1->GetURL(), page1);
150     EXPECT_EQ(host_count, RenderProcessHostCount());
151     EXPECT_NE(rph1, rph2);
152 
153     // Create another TYPE_TABBED tab.  It should share the previous process.
154     GURL page2("data:text/html,hello world2");
155     ui_test_utils::WindowedTabAddedNotificationObserver observer2(
156         content::NotificationService::AllSources());
157     chrome::ShowSingletonTab(browser(), page2);
158     observer2.Wait();
159     tab_count++;
160     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
161     tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
162     EXPECT_EQ(tab2->GetURL(), page2);
163     EXPECT_EQ(host_count, RenderProcessHostCount());
164     EXPECT_EQ(tab2->GetRenderProcessHost(), rph2);
165 
166     // Create another TYPE_WEBUI tab.  It should share the process with omnibox.
167     // Note: intentionally create this tab after the TYPE_TABBED tabs to
168     // exercise bug 43448 where extension and WebUI tabs could get combined into
169     // normal renderers.
170     GURL history(chrome::kChromeUIHistoryURL);
171     ui_test_utils::WindowedTabAddedNotificationObserver observer3(
172         content::NotificationService::AllSources());
173     chrome::ShowSingletonTab(browser(), history);
174     observer3.Wait();
175     tab_count++;
176     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
177     tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
178     EXPECT_EQ(tab2->GetURL(), GURL(history));
179     EXPECT_EQ(host_count, RenderProcessHostCount());
180     EXPECT_EQ(tab2->GetRenderProcessHost(), rph1);
181 
182     // Create a TYPE_EXTENSION tab.  It should be in its own process.
183     // (the bookmark manager is implemented as an extension)
184     GURL bookmarks(chrome::kChromeUIBookmarksURL);
185     ui_test_utils::WindowedTabAddedNotificationObserver observer4(
186         content::NotificationService::AllSources());
187     chrome::ShowSingletonTab(browser(), bookmarks);
188     observer4.Wait();
189     tab_count++;
190     host_count++;
191     EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
192     tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
193     rph3 = tab1->GetRenderProcessHost();
194     EXPECT_EQ(tab1->GetURL(), bookmarks);
195     EXPECT_EQ(host_count, RenderProcessHostCount());
196     EXPECT_NE(rph1, rph3);
197     EXPECT_NE(rph2, rph3);
198   }
199 };
200 
201 
202 class ChromeRenderProcessHostTestWithCommandLine
203     : public ChromeRenderProcessHostTest {
204  protected:
SetUpCommandLine(CommandLine * command_line)205   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
206     command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
207   }
208 };
209 
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,ProcessPerTab)210 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessPerTab) {
211   // Set max renderers to 1 to force running out of processes.
212   content::RenderProcessHost::SetMaxRendererProcessCount(1);
213 
214   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
215   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
216 
217   int tab_count = 1;
218   int host_count = 1;
219 
220   // Change the first tab to be the new tab page (TYPE_WEBUI).
221   GURL omnibox(chrome::kChromeUIOmniboxURL);
222   ui_test_utils::NavigateToURL(browser(), omnibox);
223   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
224   EXPECT_EQ(host_count, RenderProcessHostCount());
225 
226   // Create a new TYPE_TABBED tab.  It should be in its own process.
227   GURL page1("data:text/html,hello world1");
228   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
229       content::NotificationService::AllSources());
230   chrome::ShowSingletonTab(browser(), page1);
231   observer1.Wait();
232   tab_count++;
233   host_count++;
234   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
235   EXPECT_EQ(host_count, RenderProcessHostCount());
236 
237   // Create another TYPE_TABBED tab.  It should share the previous process.
238   GURL page2("data:text/html,hello world2");
239   ui_test_utils::WindowedTabAddedNotificationObserver observer2(
240       content::NotificationService::AllSources());
241   chrome::ShowSingletonTab(browser(), page2);
242   observer2.Wait();
243   tab_count++;
244   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
245   EXPECT_EQ(host_count, RenderProcessHostCount());
246 
247   // Create another omnibox tab.  It should share the process with the other
248   // WebUI.
249   ui_test_utils::NavigateToURLWithDisposition(
250       browser(), omnibox, NEW_FOREGROUND_TAB,
251       ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
252   tab_count++;
253   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
254   EXPECT_EQ(host_count, RenderProcessHostCount());
255 
256   // Create another omnibox tab.  It should share the process with the other
257   // WebUI.
258   ui_test_utils::NavigateToURLWithDisposition(
259       browser(), omnibox, NEW_FOREGROUND_TAB,
260       ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
261   tab_count++;
262   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
263   EXPECT_EQ(host_count, RenderProcessHostCount());
264 }
265 
266 // We don't change process priorities on Mac or Posix because the user lacks the
267 // permission to raise a process' priority even after lowering it.
268 #if defined(OS_WIN) || defined(OS_LINUX)
269 #if defined(OS_WIN)
270 // Flaky test: crbug.com/394368
271 #define MAYBE_Backgrounding DISABLED_Backgrounding
272 #else
273 #define MAYBE_Backgrounding Backgrounding
274 #endif
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,MAYBE_Backgrounding)275 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_Backgrounding) {
276   if (!base::Process::CanBackgroundProcesses()) {
277     LOG(ERROR) << "Can't background processes";
278     return;
279   }
280   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
281   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
282 
283   // Change the first tab to be the omnibox page (TYPE_WEBUI).
284   GURL omnibox(chrome::kChromeUIOmniboxURL);
285   ui_test_utils::NavigateToURL(browser(), omnibox);
286 
287   // Create a new tab. It should be foreground.
288   GURL page1("data:text/html,hello world1");
289   base::ProcessHandle pid1 = ShowSingletonTab(page1);
290   EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
291 
292   // Create another tab. It should be foreground, and the first tab should
293   // now be background.
294   GURL page2("data:text/html,hello world2");
295   base::ProcessHandle pid2 = ShowSingletonTab(page2);
296   EXPECT_NE(pid1, pid2);
297   EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
298   EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
299 
300   // Load another tab in background. The renderer of the new tab should be
301   // backgrounded, while visibility of the other renderers should not change.
302   GURL page3("data:text/html,hello world3");
303   base::ProcessHandle pid3 = OpenBackgroundTab(page3);
304   EXPECT_NE(pid3, pid1);
305   EXPECT_NE(pid3, pid2);
306   EXPECT_TRUE(base::Process(pid1).IsProcessBackgrounded());
307   EXPECT_FALSE(base::Process(pid2).IsProcessBackgrounded());
308   EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded());
309 
310   // Navigate back to the first page. Its renderer should be in foreground
311   // again while the other renderers should be backgrounded.
312   EXPECT_EQ(pid1, ShowSingletonTab(page1));
313   EXPECT_FALSE(base::Process(pid1).IsProcessBackgrounded());
314   EXPECT_TRUE(base::Process(pid2).IsProcessBackgrounded());
315   EXPECT_TRUE(base::Process(pid3).IsProcessBackgrounded());
316 }
317 #endif
318 
319 // TODO(nasko): crbug.com/173137
320 #if defined(OS_WIN)
321 #define MAYBE_ProcessOverflow DISABLED_ProcessOverflow
322 #else
323 #define MAYBE_ProcessOverflow ProcessOverflow
324 #endif
325 
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,MAYBE_ProcessOverflow)326 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, MAYBE_ProcessOverflow) {
327   // Set max renderers to 1 to force running out of processes.
328   content::RenderProcessHost::SetMaxRendererProcessCount(1);
329   TestProcessOverflow();
330 }
331 
332 // Variation of the ProcessOverflow test, which is driven through command line
333 // parameter instead of direct function call into the class.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,ProcessOverflow)334 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
335                        ProcessOverflow) {
336   TestProcessOverflow();
337 }
338 
339 // Ensure that DevTools opened to debug DevTools is launched in a separate
340 // process when --process-per-tab is set. See crbug.com/69873.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,DevToolsOnSelfInOwnProcessPPT)341 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
342                        DevToolsOnSelfInOwnProcessPPT) {
343 #if defined(OS_WIN) && defined(USE_ASH)
344   // Disable this test in Metro+Ash for now (http://crbug.com/262796).
345   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
346     return;
347 #endif
348 
349   CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
350   parsed_command_line.AppendSwitch(switches::kProcessPerTab);
351 
352   int tab_count = 1;
353   int host_count = 1;
354 
355   GURL page1("data:text/html,hello world1");
356   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
357       content::NotificationService::AllSources());
358   chrome::ShowSingletonTab(browser(), page1);
359   observer1.Wait();
360   tab_count++;
361   host_count++;
362   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
363   EXPECT_EQ(host_count, RenderProcessHostCount());
364 
365   // DevTools start in docked mode (no new tab), in a separate process.
366   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
367   host_count++;
368   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
369   EXPECT_EQ(host_count, RenderProcessHostCount());
370 
371   WebContents* devtools = FindFirstDevToolsContents();
372   DCHECK(devtools);
373 
374   // DevTools start in a separate process.
375   DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
376   host_count++;
377   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
378   EXPECT_EQ(host_count, RenderProcessHostCount());
379 
380   // close docked devtools
381   content::WindowedNotificationObserver close_observer(
382       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
383       content::Source<WebContents>(devtools));
384 
385   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
386   close_observer.Wait();
387 }
388 
389 // Ensure that DevTools opened to debug DevTools is launched in a separate
390 // process. See crbug.com/69873.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,DevToolsOnSelfInOwnProcess)391 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
392                        DevToolsOnSelfInOwnProcess) {
393 #if defined(OS_WIN) && defined(USE_ASH)
394   // Disable this test in Metro+Ash for now (http://crbug.com/262796).
395   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
396     return;
397 #endif
398 
399   int tab_count = 1;
400   int host_count = 1;
401 
402   GURL page1("data:text/html,hello world1");
403   ui_test_utils::WindowedTabAddedNotificationObserver observer1(
404       content::NotificationService::AllSources());
405   chrome::ShowSingletonTab(browser(), page1);
406   observer1.Wait();
407   tab_count++;
408   host_count++;
409   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
410   EXPECT_EQ(host_count, RenderProcessHostCount());
411 
412   // DevTools start in docked mode (no new tab), in a separate process.
413   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect());
414   host_count++;
415   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
416   EXPECT_EQ(host_count, RenderProcessHostCount());
417 
418   WebContents* devtools = FindFirstDevToolsContents();
419   DCHECK(devtools);
420 
421   // DevTools start in a separate process.
422   DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect());
423   host_count++;
424   EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
425   EXPECT_EQ(host_count, RenderProcessHostCount());
426 
427   // close docked devtools
428   content::WindowedNotificationObserver close_observer(
429       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
430       content::Source<content::WebContents>(devtools));
431   chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle());
432   close_observer.Wait();
433 }
434 
435 // This class's goal is to close the browser window when a renderer process has
436 // crashed. It does so by monitoring WebContents for RenderProcessGone event and
437 // closing the passed in TabStripModel. This is used in the following test case.
438 class WindowDestroyer : public content::WebContentsObserver {
439  public:
WindowDestroyer(content::WebContents * web_contents,TabStripModel * model)440   WindowDestroyer(content::WebContents* web_contents, TabStripModel* model)
441       : content::WebContentsObserver(web_contents),
442         tab_strip_model_(model) {
443   }
444 
RenderProcessGone(base::TerminationStatus status)445   virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
446     // Wait for the window to be destroyed, which will ensure all other
447     // RenderViewHost objects are deleted before we return and proceed with
448     // the next iteration of notifications.
449     content::WindowedNotificationObserver observer(
450         chrome::NOTIFICATION_BROWSER_CLOSED,
451         content::NotificationService::AllSources());
452     tab_strip_model_->CloseAllTabs();
453     observer.Wait();
454   }
455 
456  private:
457   TabStripModel* tab_strip_model_;
458 
459   DISALLOW_COPY_AND_ASSIGN(WindowDestroyer);
460 };
461 
462 // Test to ensure that while iterating through all listeners in
463 // RenderProcessHost and invalidating them, we remove them properly and don't
464 // access already freed objects. See http://crbug.com/255524.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,CloseAllTabsDuringProcessDied)465 IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
466                        CloseAllTabsDuringProcessDied) {
467   GURL url(chrome::kChromeUIOmniboxURL);
468 
469   ui_test_utils::NavigateToURL(browser(), url);
470   ui_test_utils::NavigateToURLWithDisposition(
471       browser(), url, NEW_BACKGROUND_TAB,
472       ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
473 
474   EXPECT_EQ(2, browser()->tab_strip_model()->count());
475 
476   WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);
477   WebContents* wc2 = browser()->tab_strip_model()->GetWebContentsAt(1);
478   EXPECT_EQ(wc1->GetRenderProcessHost(), wc2->GetRenderProcessHost());
479 
480   // Create an object that will close the window on a process crash.
481   WindowDestroyer destroyer(wc1, browser()->tab_strip_model());
482 
483   // Use NOTIFICATION_BROWSER_CLOSED instead of NOTIFICATION_WINDOW_CLOSED,
484   // since the latter is not implemented on OSX and the test will timeout,
485   // causing it to fail.
486   content::WindowedNotificationObserver observer(
487       chrome::NOTIFICATION_BROWSER_CLOSED,
488       content::NotificationService::AllSources());
489 
490   // Kill the renderer process, simulating a crash. This should the ProcessDied
491   // method to be called. Alternatively, RenderProcessHost::OnChannelError can
492   // be called to directly force a call to ProcessDied.
493   base::KillProcess(wc1->GetRenderProcessHost()->GetHandle(), -1, true);
494 
495   observer.Wait();
496 }
497