• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 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/message_loop/message_loop_proxy.h"
6 #include "base/synchronization/waitable_event.h"
7 #include "base/threading/thread.h"
8 #include "net/test/spawned_test_server/spawned_test_server.h"
9 #include "net/url_request/test_url_fetcher_factory.h"
10 #include "net/url_request/url_fetcher_delegate.h"
11 #include "net/url_request/url_request_test_util.h"
12 #include "sync/internal_api/public/base/cancelation_signal.h"
13 #include "sync/internal_api/public/http_bridge.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 
16 namespace syncer {
17 
18 namespace {
19 // TODO(timsteele): Should use PathService here. See Chromium Issue 3113.
20 const base::FilePath::CharType kDocRoot[] =
21     FILE_PATH_LITERAL("chrome/test/data");
22 }
23 
24 class SyncHttpBridgeTest : public testing::Test {
25  public:
SyncHttpBridgeTest()26   SyncHttpBridgeTest()
27       : test_server_(net::SpawnedTestServer::TYPE_HTTP,
28                      net::SpawnedTestServer::kLocalhost,
29                      base::FilePath(kDocRoot)),
30         fake_default_request_context_getter_(NULL),
31         bridge_for_race_test_(NULL),
32         io_thread_("IO thread") {
33   }
34 
SetUp()35   virtual void SetUp() {
36     base::Thread::Options options;
37     options.message_loop_type = base::MessageLoop::TYPE_IO;
38     io_thread_.StartWithOptions(options);
39   }
40 
TearDown()41   virtual void TearDown() {
42     if (fake_default_request_context_getter_) {
43       GetIOThreadLoop()->ReleaseSoon(FROM_HERE,
44           fake_default_request_context_getter_);
45       fake_default_request_context_getter_ = NULL;
46     }
47     io_thread_.Stop();
48   }
49 
BuildBridge()50   HttpBridge* BuildBridge() {
51     if (!fake_default_request_context_getter_) {
52       fake_default_request_context_getter_ =
53           new net::TestURLRequestContextGetter(io_thread_.message_loop_proxy());
54       fake_default_request_context_getter_->AddRef();
55     }
56     HttpBridge* bridge = new HttpBridge(
57         new HttpBridge::RequestContextGetter(
58             fake_default_request_context_getter_,
59             "user agent"),
60         NetworkTimeUpdateCallback());
61     return bridge;
62   }
63 
Abort(HttpBridge * bridge)64   static void Abort(HttpBridge* bridge) {
65     bridge->Abort();
66   }
67 
68   // Used by AbortAndReleaseBeforeFetchCompletes to test an interesting race
69   // condition.
70   void RunSyncThreadBridgeUseTest(base::WaitableEvent* signal_when_created,
71                                   base::WaitableEvent* signal_when_released);
72 
TestSameHttpNetworkSession(base::MessageLoop * main_message_loop,SyncHttpBridgeTest * test)73   static void TestSameHttpNetworkSession(base::MessageLoop* main_message_loop,
74                                          SyncHttpBridgeTest* test) {
75     scoped_refptr<HttpBridge> http_bridge(test->BuildBridge());
76     EXPECT_TRUE(test->GetTestRequestContextGetter());
77     net::HttpNetworkSession* test_session =
78         test->GetTestRequestContextGetter()->GetURLRequestContext()->
79         http_transaction_factory()->GetSession();
80     EXPECT_EQ(test_session,
81               http_bridge->GetRequestContextGetterForTest()->
82                   GetURLRequestContext()->
83                   http_transaction_factory()->GetSession());
84     main_message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
85   }
86 
GetIOThreadLoop()87   base::MessageLoop* GetIOThreadLoop() { return io_thread_.message_loop(); }
88 
89   // Note this is lazy created, so don't call this before your bridge.
GetTestRequestContextGetter()90   net::TestURLRequestContextGetter* GetTestRequestContextGetter() {
91     return fake_default_request_context_getter_;
92   }
93 
94   net::SpawnedTestServer test_server_;
95 
io_thread()96   base::Thread* io_thread() { return &io_thread_; }
97 
bridge_for_race_test()98   HttpBridge* bridge_for_race_test() { return bridge_for_race_test_; }
99 
100  private:
101   // A make-believe "default" request context, as would be returned by
102   // Profile::GetDefaultRequestContext().  Created lazily by BuildBridge.
103   net::TestURLRequestContextGetter* fake_default_request_context_getter_;
104 
105   HttpBridge* bridge_for_race_test_;
106 
107   // Separate thread for IO used by the HttpBridge.
108   base::Thread io_thread_;
109   base::MessageLoop loop_;
110 };
111 
112 // An HttpBridge that doesn't actually make network requests and just calls
113 // back with dummy response info.
114 // TODO(tim): Instead of inheriting here we should inject a component
115 // responsible for the MakeAsynchronousPost bit.
116 class ShuntedHttpBridge : public HttpBridge {
117  public:
118   // If |never_finishes| is true, the simulated request never actually
119   // returns.
ShuntedHttpBridge(net::URLRequestContextGetter * baseline_context_getter,SyncHttpBridgeTest * test,bool never_finishes)120   ShuntedHttpBridge(net::URLRequestContextGetter* baseline_context_getter,
121                     SyncHttpBridgeTest* test, bool never_finishes)
122       : HttpBridge(
123           new HttpBridge::RequestContextGetter(
124               baseline_context_getter, "user agent"),
125           NetworkTimeUpdateCallback()),
126         test_(test), never_finishes_(never_finishes) { }
127  protected:
MakeAsynchronousPost()128   virtual void MakeAsynchronousPost() OVERRIDE {
129     ASSERT_TRUE(base::MessageLoop::current() == test_->GetIOThreadLoop());
130     if (never_finishes_)
131       return;
132 
133     // We don't actually want to make a request for this test, so just callback
134     // as if it completed.
135     test_->GetIOThreadLoop()->PostTask(FROM_HERE,
136         base::Bind(&ShuntedHttpBridge::CallOnURLFetchComplete, this));
137   }
138  private:
~ShuntedHttpBridge()139   virtual ~ShuntedHttpBridge() {}
140 
CallOnURLFetchComplete()141   void CallOnURLFetchComplete() {
142     ASSERT_TRUE(base::MessageLoop::current() == test_->GetIOThreadLoop());
143     // We return no cookies and a dummy content response.
144     net::ResponseCookies cookies;
145 
146     std::string response_content = "success!";
147     net::TestURLFetcher fetcher(0, GURL(), NULL);
148     fetcher.set_url(GURL("www.google.com"));
149     fetcher.set_response_code(200);
150     fetcher.set_cookies(cookies);
151     fetcher.SetResponseString(response_content);
152     OnURLFetchComplete(&fetcher);
153   }
154   SyncHttpBridgeTest* test_;
155   bool never_finishes_;
156 };
157 
RunSyncThreadBridgeUseTest(base::WaitableEvent * signal_when_created,base::WaitableEvent * signal_when_released)158 void SyncHttpBridgeTest::RunSyncThreadBridgeUseTest(
159     base::WaitableEvent* signal_when_created,
160     base::WaitableEvent* signal_when_released) {
161   scoped_refptr<net::URLRequestContextGetter> ctx_getter(
162       new net::TestURLRequestContextGetter(io_thread_.message_loop_proxy()));
163   {
164     scoped_refptr<ShuntedHttpBridge> bridge(
165         new ShuntedHttpBridge(ctx_getter.get(), this, true));
166     bridge->SetURL("http://www.google.com", 9999);
167     bridge->SetPostPayload("text/plain", 2, " ");
168     bridge_for_race_test_ = bridge.get();
169     signal_when_created->Signal();
170 
171     int os_error = 0;
172     int response_code = 0;
173     bridge->MakeSynchronousPost(&os_error, &response_code);
174     bridge_for_race_test_ = NULL;
175   }
176   signal_when_released->Signal();
177 }
178 
TEST_F(SyncHttpBridgeTest,TestUsesSameHttpNetworkSession)179 TEST_F(SyncHttpBridgeTest, TestUsesSameHttpNetworkSession) {
180   // Run this test on the IO thread because we can only call
181   // URLRequestContextGetter::GetURLRequestContext on the IO thread.
182   io_thread()->message_loop()
183       ->PostTask(FROM_HERE,
184                  base::Bind(&SyncHttpBridgeTest::TestSameHttpNetworkSession,
185                             base::MessageLoop::current(),
186                             this));
187   base::MessageLoop::current()->Run();
188 }
189 
190 // Test the HttpBridge without actually making any network requests.
TEST_F(SyncHttpBridgeTest,TestMakeSynchronousPostShunted)191 TEST_F(SyncHttpBridgeTest, TestMakeSynchronousPostShunted) {
192   scoped_refptr<net::URLRequestContextGetter> ctx_getter(
193       new net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
194   scoped_refptr<HttpBridge> http_bridge(
195       new ShuntedHttpBridge(ctx_getter.get(), this, false));
196   http_bridge->SetURL("http://www.google.com", 9999);
197   http_bridge->SetPostPayload("text/plain", 2, " ");
198 
199   int os_error = 0;
200   int response_code = 0;
201   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
202   EXPECT_TRUE(success);
203   EXPECT_EQ(200, response_code);
204   EXPECT_EQ(0, os_error);
205 
206   EXPECT_EQ(8, http_bridge->GetResponseContentLength());
207   EXPECT_EQ(std::string("success!"),
208             std::string(http_bridge->GetResponseContent()));
209 }
210 
211 // Full round-trip test of the HttpBridge, using default UA string and
212 // no request cookies.
TEST_F(SyncHttpBridgeTest,TestMakeSynchronousPostLiveWithPayload)213 TEST_F(SyncHttpBridgeTest, TestMakeSynchronousPostLiveWithPayload) {
214   ASSERT_TRUE(test_server_.Start());
215 
216   scoped_refptr<HttpBridge> http_bridge(BuildBridge());
217 
218   std::string payload = "this should be echoed back";
219   GURL echo = test_server_.GetURL("echo");
220   http_bridge->SetURL(echo.spec().c_str(), echo.IntPort());
221   http_bridge->SetPostPayload("application/x-www-form-urlencoded",
222                               payload.length() + 1, payload.c_str());
223   int os_error = 0;
224   int response_code = 0;
225   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
226   EXPECT_TRUE(success);
227   EXPECT_EQ(200, response_code);
228   EXPECT_EQ(0, os_error);
229 
230   EXPECT_EQ(payload.length() + 1,
231             static_cast<size_t>(http_bridge->GetResponseContentLength()));
232   EXPECT_EQ(payload, std::string(http_bridge->GetResponseContent()));
233 }
234 
235 // Full round-trip test of the HttpBridge.
TEST_F(SyncHttpBridgeTest,TestMakeSynchronousPostLiveComprehensive)236 TEST_F(SyncHttpBridgeTest, TestMakeSynchronousPostLiveComprehensive) {
237   ASSERT_TRUE(test_server_.Start());
238 
239   scoped_refptr<HttpBridge> http_bridge(BuildBridge());
240 
241   GURL echo_header = test_server_.GetURL("echoall");
242   http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
243 
244   std::string test_payload = "###TEST PAYLOAD###";
245   http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
246                               test_payload.c_str());
247 
248   int os_error = 0;
249   int response_code = 0;
250   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
251   EXPECT_TRUE(success);
252   EXPECT_EQ(200, response_code);
253   EXPECT_EQ(0, os_error);
254 
255   std::string response(http_bridge->GetResponseContent(),
256                        http_bridge->GetResponseContentLength());
257   EXPECT_EQ(std::string::npos, response.find("Cookie:"));
258   EXPECT_NE(std::string::npos, response.find("User-Agent: user agent"));
259   EXPECT_NE(std::string::npos, response.find(test_payload.c_str()));
260 }
261 
TEST_F(SyncHttpBridgeTest,TestExtraRequestHeaders)262 TEST_F(SyncHttpBridgeTest, TestExtraRequestHeaders) {
263   ASSERT_TRUE(test_server_.Start());
264 
265   scoped_refptr<HttpBridge> http_bridge(BuildBridge());
266 
267   GURL echo_header = test_server_.GetURL("echoall");
268 
269   http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
270   http_bridge->SetExtraRequestHeaders("test:fnord");
271 
272   std::string test_payload = "###TEST PAYLOAD###";
273   http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
274                               test_payload.c_str());
275 
276   int os_error = 0;
277   int response_code = 0;
278   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
279   EXPECT_TRUE(success);
280   EXPECT_EQ(200, response_code);
281   EXPECT_EQ(0, os_error);
282 
283   std::string response(http_bridge->GetResponseContent(),
284                        http_bridge->GetResponseContentLength());
285 
286   EXPECT_NE(std::string::npos, response.find("fnord"));
287   EXPECT_NE(std::string::npos, response.find(test_payload.c_str()));
288 }
289 
TEST_F(SyncHttpBridgeTest,TestResponseHeader)290 TEST_F(SyncHttpBridgeTest, TestResponseHeader) {
291   ASSERT_TRUE(test_server_.Start());
292 
293   scoped_refptr<HttpBridge> http_bridge(BuildBridge());
294 
295   GURL echo_header = test_server_.GetURL("echoall");
296   http_bridge->SetURL(echo_header.spec().c_str(), echo_header.IntPort());
297 
298   std::string test_payload = "###TEST PAYLOAD###";
299   http_bridge->SetPostPayload("text/html", test_payload.length() + 1,
300                               test_payload.c_str());
301 
302   int os_error = 0;
303   int response_code = 0;
304   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
305   EXPECT_TRUE(success);
306   EXPECT_EQ(200, response_code);
307   EXPECT_EQ(0, os_error);
308 
309   EXPECT_EQ(http_bridge->GetResponseHeaderValue("Content-type"), "text/html");
310   EXPECT_TRUE(http_bridge->GetResponseHeaderValue("invalid-header").empty());
311 }
312 
TEST_F(SyncHttpBridgeTest,Abort)313 TEST_F(SyncHttpBridgeTest, Abort) {
314   scoped_refptr<net::URLRequestContextGetter> ctx_getter(
315       new net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
316   scoped_refptr<ShuntedHttpBridge> http_bridge(
317       new ShuntedHttpBridge(ctx_getter.get(), this, true));
318   http_bridge->SetURL("http://www.google.com", 9999);
319   http_bridge->SetPostPayload("text/plain", 2, " ");
320 
321   int os_error = 0;
322   int response_code = 0;
323 
324   io_thread()->message_loop_proxy()->PostTask(
325       FROM_HERE,
326       base::Bind(&SyncHttpBridgeTest::Abort, http_bridge));
327   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
328   EXPECT_FALSE(success);
329   EXPECT_EQ(net::ERR_ABORTED, os_error);
330 }
331 
TEST_F(SyncHttpBridgeTest,AbortLate)332 TEST_F(SyncHttpBridgeTest, AbortLate) {
333   scoped_refptr<net::URLRequestContextGetter> ctx_getter(
334       new net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
335   scoped_refptr<ShuntedHttpBridge> http_bridge(
336       new ShuntedHttpBridge(ctx_getter.get(), this, false));
337   http_bridge->SetURL("http://www.google.com", 9999);
338   http_bridge->SetPostPayload("text/plain", 2, " ");
339 
340   int os_error = 0;
341   int response_code = 0;
342 
343   bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code);
344   ASSERT_TRUE(success);
345   http_bridge->Abort();
346   // Ensures no double-free of URLFetcher, etc.
347 }
348 
349 // Tests an interesting case where code using the HttpBridge aborts the fetch
350 // and releases ownership before a pending fetch completed callback is issued by
351 // the underlying URLFetcher (and before that URLFetcher is destroyed, which
352 // would cancel the callback).
TEST_F(SyncHttpBridgeTest,AbortAndReleaseBeforeFetchComplete)353 TEST_F(SyncHttpBridgeTest, AbortAndReleaseBeforeFetchComplete) {
354   base::Thread sync_thread("SyncThread");
355   sync_thread.Start();
356 
357   // First, block the sync thread on the post.
358   base::WaitableEvent signal_when_created(false, false);
359   base::WaitableEvent signal_when_released(false, false);
360   sync_thread.message_loop()->PostTask(FROM_HERE,
361       base::Bind(&SyncHttpBridgeTest::RunSyncThreadBridgeUseTest,
362                  base::Unretained(this),
363                  &signal_when_created,
364                  &signal_when_released));
365 
366   // Stop IO so we can control order of operations.
367   base::WaitableEvent io_waiter(false, false);
368   ASSERT_TRUE(io_thread()->message_loop_proxy()->PostTask(
369       FROM_HERE,
370       base::Bind(&base::WaitableEvent::Wait, base::Unretained(&io_waiter))));
371 
372   signal_when_created.Wait();  // Wait till we have a bridge to abort.
373   ASSERT_TRUE(bridge_for_race_test());
374 
375   // Schedule the fetch completion callback (but don't run it yet). Don't take
376   // a reference to the bridge to mimic URLFetcher's handling of the delegate.
377   net::URLFetcherDelegate* delegate =
378       static_cast<net::URLFetcherDelegate*>(bridge_for_race_test());
379   net::ResponseCookies cookies;
380   std::string response_content = "success!";
381   net::TestURLFetcher fetcher(0, GURL(), NULL);
382   fetcher.set_url(GURL("www.google.com"));
383   fetcher.set_response_code(200);
384   fetcher.set_cookies(cookies);
385   fetcher.SetResponseString(response_content);
386   ASSERT_TRUE(io_thread()->message_loop_proxy()->PostTask(
387       FROM_HERE,
388       base::Bind(&net::URLFetcherDelegate::OnURLFetchComplete,
389           base::Unretained(delegate), &fetcher)));
390 
391   // Abort the fetch. This should be smart enough to handle the case where
392   // the bridge is destroyed before the callback scheduled above completes.
393   bridge_for_race_test()->Abort();
394 
395   // Wait until the sync thread releases its ref on the bridge.
396   signal_when_released.Wait();
397   ASSERT_FALSE(bridge_for_race_test());
398 
399   // Unleash the hounds. The fetch completion callback should fire first, and
400   // succeed even though we Release()d the bridge above because the call to
401   // Abort should have held a reference.
402   io_waiter.Signal();
403 
404   // Done.
405   sync_thread.Stop();
406   io_thread()->Stop();
407 }
408 
HttpBridgeRunOnSyncThread(net::URLRequestContextGetter * baseline_context_getter,CancelationSignal * factory_cancelation_signal,syncer::HttpPostProviderFactory ** bridge_factory_out,syncer::HttpPostProviderInterface ** bridge_out,base::WaitableEvent * signal_when_created,base::WaitableEvent * wait_for_shutdown)409 void HttpBridgeRunOnSyncThread(
410     net::URLRequestContextGetter* baseline_context_getter,
411     CancelationSignal* factory_cancelation_signal,
412     syncer::HttpPostProviderFactory** bridge_factory_out,
413     syncer::HttpPostProviderInterface** bridge_out,
414     base::WaitableEvent* signal_when_created,
415     base::WaitableEvent* wait_for_shutdown) {
416   scoped_ptr<syncer::HttpBridgeFactory> bridge_factory(
417       new syncer::HttpBridgeFactory(baseline_context_getter,
418                                     NetworkTimeUpdateCallback(),
419                                     factory_cancelation_signal));
420   bridge_factory->Init("test");
421   *bridge_factory_out = bridge_factory.get();
422 
423   HttpPostProviderInterface* bridge = bridge_factory->Create();
424   *bridge_out = bridge;
425 
426   signal_when_created->Signal();
427   wait_for_shutdown->Wait();
428 
429   bridge_factory->Destroy(bridge);
430 }
431 
WaitOnIOThread(base::WaitableEvent * signal_wait_start,base::WaitableEvent * wait_done)432 void WaitOnIOThread(base::WaitableEvent* signal_wait_start,
433                     base::WaitableEvent* wait_done) {
434   signal_wait_start->Signal();
435   wait_done->Wait();
436 }
437 
438 // Tests RequestContextGetter is properly released on IO thread even when
439 // IO thread stops before sync thread.
TEST_F(SyncHttpBridgeTest,RequestContextGetterReleaseOrder)440 TEST_F(SyncHttpBridgeTest, RequestContextGetterReleaseOrder) {
441   base::Thread sync_thread("SyncThread");
442   sync_thread.Start();
443 
444   syncer::HttpPostProviderFactory* factory = NULL;
445   syncer::HttpPostProviderInterface* bridge = NULL;
446 
447   scoped_refptr<net::URLRequestContextGetter> baseline_context_getter(
448       new net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
449 
450   base::WaitableEvent signal_when_created(false, false);
451   base::WaitableEvent wait_for_shutdown(false, false);
452 
453   CancelationSignal release_request_context_signal;
454 
455   // Create bridge factory and factory on sync thread and wait for the creation
456   // to finish.
457   sync_thread.message_loop()->PostTask(FROM_HERE,
458       base::Bind(&HttpBridgeRunOnSyncThread,
459                  base::Unretained(baseline_context_getter.get()),
460                  &release_request_context_signal ,&factory, &bridge,
461                  &signal_when_created, &wait_for_shutdown));
462   signal_when_created.Wait();
463 
464   // Simulate sync shutdown by aborting bridge and shutting down factory on
465   // frontend.
466   bridge->Abort();
467   release_request_context_signal.Signal();
468 
469   // Wait for sync's RequestContextGetter to be cleared on IO thread and
470   // check for reference count.
471   base::WaitableEvent signal_wait_start(false, false);
472   base::WaitableEvent wait_done(false, false);
473   io_thread()->message_loop()->PostTask(
474       FROM_HERE,
475       base::Bind(&WaitOnIOThread, &signal_wait_start, &wait_done));
476   signal_wait_start.Wait();
477   // |baseline_context_getter| should have only one reference from local
478   // variable.
479   EXPECT_TRUE(baseline_context_getter->HasOneRef());
480   baseline_context_getter = NULL;
481 
482   // Unblock and stop IO thread before sync thread.
483   wait_done.Signal();
484   io_thread()->Stop();
485 
486   // Unblock and stop sync thread.
487   wait_for_shutdown.Signal();
488   sync_thread.Stop();
489 }
490 
491 // Attempt to release the URLRequestContextGetter before the HttpBridgeFactory
492 // is initialized.
TEST_F(SyncHttpBridgeTest,EarlyAbortFactory)493 TEST_F(SyncHttpBridgeTest, EarlyAbortFactory) {
494   // In a real scenario, the following would happen on many threads.  For
495   // simplicity, this test uses only one thread.
496 
497   scoped_refptr<net::URLRequestContextGetter> baseline_context_getter(
498       new net::TestURLRequestContextGetter(io_thread()->message_loop_proxy()));
499   CancelationSignal release_request_context_signal;
500 
501   // UI Thread: Initialize the HttpBridgeFactory.  The next step would be to
502   // post a task to SBH::Core to have it initialized.
503   scoped_ptr<syncer::HttpBridgeFactory> factory(new HttpBridgeFactory(
504       baseline_context_getter,
505       NetworkTimeUpdateCallback(),
506       &release_request_context_signal));
507 
508   // UI Thread: A very early shutdown request arrives and executes on the UI
509   // thread before the posted sync thread task is run.
510   release_request_context_signal.Signal();
511 
512   // Sync thread: Finally run the posted task, only to find that our
513   // HttpBridgeFactory has been neutered.  Should not crash.
514   factory->Init("TestUserAgent");
515 
516   // At this point, attempting to use the factory would trigger a crash.  Both
517   // this test and the real world code should make sure this never happens.
518 };
519 
520 }  // namespace syncer
521