1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <thread>
18
19 #include <folly/MPMCQueue.h>
20 #include <folly/executors/DrivableExecutor.h>
21 #include <folly/executors/InlineExecutor.h>
22 #include <folly/executors/ManualExecutor.h>
23 #include <folly/futures/Future.h>
24 #include <folly/portability/GTest.h>
25 #include <folly/synchronization/Baton.h>
26
27 using namespace folly;
28
29 struct ManualWaiter : public DrivableExecutor {
ManualWaiterManualWaiter30 explicit ManualWaiter(std::shared_ptr<ManualExecutor> ex_) : ex(ex_) {}
31
addManualWaiter32 void add(Func f) override { ex->add(std::move(f)); }
33
driveManualWaiter34 void drive() override {
35 ex->wait();
36 ex->run();
37 }
38
39 std::shared_ptr<ManualExecutor> ex;
40 };
41
42 struct ViaFixture : public testing::Test {
ViaFixtureViaFixture43 ViaFixture()
44 : westExecutor(new ManualExecutor),
45 eastExecutor(new ManualExecutor),
46 waiter(new ManualWaiter(westExecutor)),
47 done(false) {
48 th = std::thread([=] {
49 ManualWaiter eastWaiter(eastExecutor);
50 while (!done) {
51 eastWaiter.drive();
52 }
53 });
54 }
55
~ViaFixtureViaFixture56 ~ViaFixture() override {
57 done = true;
58 eastExecutor->add([=]() {});
59 th.join();
60 }
61
addAsyncViaFixture62 void addAsync(int a, int b, std::function<void(int&&)>&& cob) {
63 eastExecutor->add([=]() { cob(a + b); });
64 }
65
66 std::shared_ptr<ManualExecutor> westExecutor;
67 std::shared_ptr<ManualExecutor> eastExecutor;
68 std::shared_ptr<ManualWaiter> waiter;
69 InlineExecutor inlineExecutor;
70 std::atomic<bool> done;
71 std::thread th;
72 };
73
TEST(Via,exceptionOnLaunch)74 TEST(Via, exceptionOnLaunch) {
75 auto future = makeFuture<int>(std::runtime_error("E"));
76 EXPECT_THROW(future.value(), std::runtime_error);
77 }
78
TEST(Via,thenValue)79 TEST(Via, thenValue) {
80 auto future = makeFuture(std::move(1)).thenTry([](Try<int>&& t) {
81 return t.value() == 1;
82 });
83
84 EXPECT_TRUE(future.value());
85 }
86
TEST(Via,thenFuture)87 TEST(Via, thenFuture) {
88 auto future = makeFuture(1).thenTry(
89 [](Try<int>&& t) { return makeFuture(t.value() == 1); });
90 EXPECT_TRUE(future.value());
91 }
92
doWorkStatic(Try<std::string> && t)93 static Future<std::string> doWorkStatic(Try<std::string>&& t) {
94 return makeFuture(t.value() + ";static");
95 }
96
TEST(Via,thenFunction)97 TEST(Via, thenFunction) {
98 struct Worker {
99 Future<std::string> doWork(Try<std::string>&& t) {
100 return makeFuture(t.value() + ";class");
101 }
102 static Future<std::string> doWorkStatic(Try<std::string>&& t) {
103 return makeFuture(t.value() + ";class-static");
104 }
105 } w;
106
107 auto f = makeFuture(std::string("start"))
108 .thenTry(doWorkStatic)
109 .thenTry(Worker::doWorkStatic)
110 .then(&Worker::doWork, &w);
111
112 EXPECT_EQ(f.value(), "start;static;class-static;class");
113 }
114
TEST_F(ViaFixture,threadHops)115 TEST_F(ViaFixture, threadHops) {
116 auto westThreadId = std::this_thread::get_id();
117 auto f = via(eastExecutor.get())
118 .thenTry([=](Try<Unit>&& /* t */) {
119 EXPECT_NE(std::this_thread::get_id(), westThreadId);
120 return makeFuture<int>(1);
121 })
122 .via(westExecutor.get())
123 .thenTry([=](Try<int>&& t) {
124 EXPECT_EQ(std::this_thread::get_id(), westThreadId);
125 return t.value();
126 });
127 EXPECT_EQ(std::move(f).getVia(waiter.get()), 1);
128 }
129
TEST_F(ViaFixture,chainVias)130 TEST_F(ViaFixture, chainVias) {
131 auto westThreadId = std::this_thread::get_id();
132 auto f = via(eastExecutor.get())
133 .thenValue([=](auto&&) {
134 EXPECT_NE(std::this_thread::get_id(), westThreadId);
135 return 1;
136 })
137 .thenValue([=](int val) {
138 return makeFuture(val)
139 .via(westExecutor.get())
140 .thenValue([=](int v) mutable {
141 EXPECT_EQ(std::this_thread::get_id(), westThreadId);
142 return v + 1;
143 });
144 })
145 .thenValue([=](int val) {
146 // even though ultimately the future that triggers this one
147 // executed in the west thread, this thenValue() inherited the
148 // executor from its predecessor, ie the eastExecutor.
149 EXPECT_NE(std::this_thread::get_id(), westThreadId);
150 return val + 1;
151 })
152 .via(westExecutor.get())
153 .thenValue([=](int val) {
154 // go back to west, so we can wait on it
155 EXPECT_EQ(std::this_thread::get_id(), westThreadId);
156 return val + 1;
157 });
158
159 EXPECT_EQ(std::move(f).getVia(waiter.get()), 4);
160 }
161
TEST_F(ViaFixture,bareViaAssignment)162 TEST_F(ViaFixture, bareViaAssignment) {
163 auto f = via(eastExecutor.get());
164 }
TEST_F(ViaFixture,viaAssignment)165 TEST_F(ViaFixture, viaAssignment) {
166 // via()&&
167 auto f = makeFuture().via(eastExecutor.get());
168 // via()&
169 auto f2 = f.via(eastExecutor.get());
170 }
171
172 struct PriorityExecutor : public Executor {
addPriorityExecutor173 void add(Func /* f */) override { count1++; }
174
addWithPriorityPriorityExecutor175 void addWithPriority(Func f, int8_t priority) override {
176 int mid = getNumPriorities() / 2;
177 int p = priority < 0 ? std::max(0, mid + priority)
178 : std::min(getNumPriorities() - 1, mid + priority);
179 EXPECT_LT(p, 3);
180 EXPECT_GE(p, 0);
181 if (p == 0) {
182 count0++;
183 } else if (p == 1) {
184 count1++;
185 } else if (p == 2) {
186 count2++;
187 }
188 f();
189 }
190
getNumPrioritiesPriorityExecutor191 uint8_t getNumPriorities() const override { return 3; }
192
193 int count0{0};
194 int count1{0};
195 int count2{0};
196 };
197
TEST(Via,priority)198 TEST(Via, priority) {
199 PriorityExecutor exe;
200 via(&exe, -1).thenValue([](auto&&) {});
201 via(&exe, 0).thenValue([](auto&&) {});
202 via(&exe, 1).thenValue([](auto&&) {});
203 via(&exe, 42).thenValue([](auto&&) {}); // overflow should go to max priority
204 via(&exe, -42).thenValue(
205 [](auto&&) {}); // underflow should go to min priority
206 via(&exe).thenValue([](auto&&) {}); // default to mid priority
207 via(&exe, Executor::LO_PRI).thenValue([](auto&&) {});
208 via(&exe, Executor::HI_PRI).thenValue([](auto&&) {});
209 EXPECT_EQ(3, exe.count0);
210 EXPECT_EQ(2, exe.count1);
211 EXPECT_EQ(3, exe.count2);
212 }
213
TEST(Via,then2)214 TEST(Via, then2) {
215 ManualExecutor x1, x2;
216 bool a = false, b = false, c = false;
217 via(&x1)
218 .thenValue([&](auto&&) { a = true; })
219 .thenValue([&](auto&&) { b = true; })
220 .thenValueInline(folly::makeAsyncTask(&x2, [&](auto&&) { c = true; }));
221
222 EXPECT_FALSE(a);
223 EXPECT_FALSE(b);
224
225 x1.run();
226 EXPECT_TRUE(a);
227 EXPECT_FALSE(b);
228 EXPECT_FALSE(c);
229
230 x1.run();
231 EXPECT_TRUE(b);
232 EXPECT_FALSE(c);
233
234 x2.run();
235 EXPECT_TRUE(c);
236 }
237
TEST(Via,allowInline)238 TEST(Via, allowInline) {
239 ManualExecutor x1, x2;
240 bool a = false, b = false, c = false, d = false, e = false, f = false,
241 g = false, h = false, i = false, j = false, k = false, l = false,
242 m = false, n = false, o = false, p = false, q = false, r = false;
243 via(&x1)
244 .thenValue([&](auto&&) { a = true; })
245 .thenTryInline([&](auto&&) { b = true; })
246 .thenTry([&](auto&&) { c = true; })
247 .via(&x2)
248 .thenTryInline([&](auto&&) { d = true; })
249 .thenValue([&](auto&&) {
250 e = true;
251 return via(&x2).thenValue([&](auto&&) { f = true; });
252 })
253 .thenValueInline([&](auto&&) { g = true; })
254 .thenValue([&](auto&&) {
255 h = true;
256 return via(&x1).thenValue([&](auto&&) { i = true; });
257 })
258 .thenValueInline([&](auto&&) { j = true; })
259 .semi()
260 .deferValue([&](auto&&) { k = true; })
261 .via(&x2)
262 .thenValueInline([&](auto&&) { l = true; })
263 .semi()
264 .deferValue([&](auto&&) { m = true; })
265 .via(&x1)
266 .thenValue([&](auto&&) { n = true; })
267 .semi()
268 .deferValue([&](auto&&) { o = true; })
269 .deferValue([&](auto&&) { p = true; })
270 .via(&x1)
271 .semi()
272 .deferValue([&](auto&&) { q = true; })
273 .deferValue([&](auto&&) { r = true; })
274 .via(&x2);
275
276 EXPECT_FALSE(a);
277 EXPECT_FALSE(b);
278
279 // Expect b to be satisfied inline with the task x1
280 x1.run();
281 EXPECT_TRUE(a);
282 EXPECT_TRUE(b);
283 EXPECT_FALSE(c);
284
285 x1.run();
286 EXPECT_TRUE(c);
287 EXPECT_FALSE(d);
288
289 // Demonstrate that the executor transition did not allow inline execution
290 x2.run();
291 EXPECT_TRUE(d);
292 EXPECT_FALSE(e);
293
294 x2.run();
295 EXPECT_TRUE(e);
296 EXPECT_FALSE(f);
297 EXPECT_FALSE(g);
298
299 // Completing nested continuation should satisfy inline continuation
300 x2.run();
301 EXPECT_TRUE(f);
302 EXPECT_TRUE(g);
303 EXPECT_FALSE(h);
304
305 x2.run();
306 EXPECT_TRUE(h);
307 EXPECT_FALSE(i);
308 EXPECT_FALSE(j);
309
310 // Nested continuation on different executor should not complete next entry
311 // inline
312 x1.run();
313 EXPECT_TRUE(i);
314 EXPECT_FALSE(j);
315
316 // Defer should run on x1 and therefore not inline
317 // Subsequent deferred work is run on x1 and hence not inlined.
318 x2.run();
319 EXPECT_TRUE(j);
320 EXPECT_TRUE(k);
321 EXPECT_TRUE(l);
322 EXPECT_FALSE(m);
323
324 // Complete the deferred task
325 x1.run();
326 EXPECT_TRUE(m);
327 EXPECT_FALSE(n);
328
329 // Here defer and the above thenValue are both on x1, defer should be
330 // inline
331 x1.run();
332 EXPECT_TRUE(n);
333 EXPECT_TRUE(o);
334 EXPECT_TRUE(p);
335 EXPECT_FALSE(q);
336
337 // Change of executor in deferred executor so now run x2 to complete
338 x2.run();
339 EXPECT_TRUE(q);
340 EXPECT_TRUE(r);
341 }
342
343 #ifndef __APPLE__ // TODO #7372389
344 /// Simple executor that does work in another thread
345 class ThreadExecutor : public Executor {
346 folly::MPMCQueue<Func> funcs;
347 std::atomic<bool> done{false};
348 std::thread worker;
349 folly::Baton<> baton;
350
work()351 void work() {
352 baton.post();
353 Func fn;
354 while (!done) {
355 while (!funcs.isEmpty()) {
356 funcs.blockingRead(fn);
357 fn();
358 }
359 }
360 }
361
362 public:
ThreadExecutor(size_t n=1024)363 explicit ThreadExecutor(size_t n = 1024) : funcs(n) {
364 worker = std::thread(std::bind(&ThreadExecutor::work, this));
365 }
366
~ThreadExecutor()367 ~ThreadExecutor() override {
368 done = true;
369 funcs.write([] {});
370 worker.join();
371 }
372
add(Func fn)373 void add(Func fn) override { funcs.blockingWrite(std::move(fn)); }
374
waitForStartup()375 void waitForStartup() { baton.wait(); }
376 };
377
TEST(Via,viaThenGetWasRacy)378 TEST(Via, viaThenGetWasRacy) {
379 ThreadExecutor x;
380 std::unique_ptr<int> val =
381 folly::via(&x)
382 .thenValue([](auto&&) { return std::make_unique<int>(42); })
383 .get();
384 ASSERT_TRUE(!!val);
385 EXPECT_EQ(42, *val);
386 }
387
TEST(Via,callbackRace)388 TEST(Via, callbackRace) {
389 ThreadExecutor x;
390
391 auto fn = [&x] {
392 auto promises = std::make_shared<std::vector<Promise<Unit>>>(4);
393 std::vector<Future<Unit>> futures;
394
395 for (auto& p : *promises) {
396 futures.emplace_back(p.getFuture().via(&x).thenTry([](Try<Unit>&&) {}));
397 }
398
399 x.waitForStartup();
400 x.add([promises] {
401 for (auto& p : *promises) {
402 p.setValue();
403 }
404 });
405
406 return collectAll(futures);
407 };
408
409 fn().wait();
410 }
411 #endif
412
413 class DummyDrivableExecutor : public DrivableExecutor {
414 public:
add(Func)415 void add(Func /* f */) override {}
drive()416 void drive() override { ran = true; }
417 bool ran{false};
418 };
419
TEST(Via,getVia)420 TEST(Via, getVia) {
421 {
422 // non-void
423 ManualExecutor x;
424 auto f = via(&x).thenValue([](auto&&) { return true; });
425 EXPECT_TRUE(std::move(f).getVia(&x));
426 }
427
428 {
429 // void
430 ManualExecutor x;
431 auto f = via(&x).then();
432 std::move(f).getVia(&x);
433 }
434
435 {
436 DummyDrivableExecutor x;
437 auto f = makeFuture(true);
438 EXPECT_TRUE(std::move(f).getVia(&x));
439 EXPECT_FALSE(x.ran);
440 }
441 }
442
TEST(Via,SimpleTimedGetVia)443 TEST(Via, SimpleTimedGetVia) {
444 TimedDrivableExecutor e2;
445 Promise<folly::Unit> p;
446 auto f = p.getFuture();
447 EXPECT_THROW(
448 std::move(f).getVia(&e2, std::chrono::seconds(1)), FutureTimeout);
449 }
450
TEST(Via,getTryVia)451 TEST(Via, getTryVia) {
452 {
453 // non-void
454 ManualExecutor x;
455 auto f = via(&x).thenValue([](auto&&) { return 23; });
456 EXPECT_FALSE(f.isReady());
457 EXPECT_EQ(23, std::move(f).getTryVia(&x).value());
458 }
459
460 {
461 // void
462 ManualExecutor x;
463 auto f = via(&x).then();
464 EXPECT_FALSE(f.isReady());
465 auto t = std::move(f).getTryVia(&x);
466 EXPECT_TRUE(t.hasValue());
467 }
468
469 {
470 DummyDrivableExecutor x;
471 auto f = makeFuture(23);
472 EXPECT_EQ(23, std::move(f).getTryVia(&x).value());
473 EXPECT_FALSE(x.ran);
474 }
475 }
476
TEST(Via,SimpleTimedGetTryVia)477 TEST(Via, SimpleTimedGetTryVia) {
478 TimedDrivableExecutor e2;
479 Promise<folly::Unit> p;
480 auto f = p.getFuture();
481 EXPECT_THROW(
482 std::move(f).getTryVia(&e2, std::chrono::seconds(1)), FutureTimeout);
483 }
484
TEST(Via,waitVia)485 TEST(Via, waitVia) {
486 {
487 ManualExecutor x;
488 auto f = via(&x).then();
489 EXPECT_FALSE(f.isReady());
490 f.waitVia(&x);
491 EXPECT_TRUE(f.isReady());
492 }
493
494 {
495 // try rvalue as well
496 ManualExecutor x;
497 auto f = via(&x).then().waitVia(&x);
498 EXPECT_TRUE(f.isReady());
499 }
500
501 {
502 DummyDrivableExecutor x;
503 makeFuture(true).waitVia(&x);
504 EXPECT_FALSE(x.ran);
505 }
506 }
507
TEST(Via,viaRaces)508 TEST(Via, viaRaces) {
509 ManualExecutor x;
510 Promise<Unit> p;
511 auto tid = std::this_thread::get_id();
512 bool done = false;
513
514 std::thread t1([&] {
515 p.getFuture()
516 .via(&x)
517 .thenTry(
518 [&](Try<Unit>&&) { EXPECT_EQ(tid, std::this_thread::get_id()); })
519 .thenTry(
520 [&](Try<Unit>&&) { EXPECT_EQ(tid, std::this_thread::get_id()); })
521 .thenTry([&](Try<Unit>&&) { done = true; });
522 });
523
524 std::thread t2([&] { p.setValue(); });
525
526 while (!done) {
527 x.run();
528 }
529 t1.join();
530 t2.join();
531 }
532
TEST(Via,viaDummyExecutorFutureSetValueFirst)533 TEST(Via, viaDummyExecutorFutureSetValueFirst) {
534 // The callback object will get destroyed when passed to the executor.
535
536 // A promise will be captured by the callback lambda so we can observe that
537 // it will be destroyed.
538 Promise<Unit> captured_promise;
539 auto captured_promise_future = captured_promise.getFuture();
540
541 DummyDrivableExecutor x;
542 auto future = makeFuture().via(&x).thenValue(
543 [c = std::move(captured_promise)](auto&&) { return 42; });
544
545 EXPECT_THROW(std::move(future).get(std::chrono::seconds(5)), BrokenPromise);
546 EXPECT_THROW(
547 std::move(captured_promise_future).get(std::chrono::seconds(5)),
548 BrokenPromise);
549 }
550
TEST(Via,viaDummyExecutorFutureSetCallbackFirst)551 TEST(Via, viaDummyExecutorFutureSetCallbackFirst) {
552 // The callback object will get destroyed when passed to the executor.
553
554 // A promise will be captured by the callback lambda so we can observe that
555 // it will be destroyed.
556 Promise<Unit> captured_promise;
557 auto captured_promise_future = captured_promise.getFuture();
558
559 DummyDrivableExecutor x;
560 Promise<Unit> trigger;
561 auto future = trigger.getFuture().via(&x).thenValue(
562 [c = std::move(captured_promise)](auto&&) { return 42; });
563 trigger.setValue();
564
565 EXPECT_THROW(std::move(future).get(std::chrono::seconds(5)), BrokenPromise);
566 EXPECT_THROW(
567 std::move(captured_promise_future).get(std::chrono::seconds(5)),
568 BrokenPromise);
569 }
570
TEST(Via,viaExecutorDiscardsTaskFutureSetValueFirst)571 TEST(Via, viaExecutorDiscardsTaskFutureSetValueFirst) {
572 // The callback object will get destroyed when the ManualExecutor runs out
573 // of scope.
574
575 // A promise will be captured by the callback lambda so we can observe that
576 // it will be destroyed.
577 Promise<Unit> captured_promise;
578 auto captured_promise_future = captured_promise.getFuture();
579
580 Optional<SemiFuture<int>> future;
581 {
582 ManualExecutor x;
583 future =
584 makeFuture()
585 .via(&x)
586 .thenValue([c = std::move(captured_promise)](auto&&) { return 42; })
587 .semi();
588 x.clear();
589 }
590
591 EXPECT_THROW(std::move(*future).get(std::chrono::seconds(5)), BrokenPromise);
592 EXPECT_THROW(
593 std::move(captured_promise_future).get(std::chrono::seconds(5)),
594 BrokenPromise);
595 }
596
TEST(Via,viaExecutorDiscardsTaskFutureSetCallbackFirst)597 TEST(Via, viaExecutorDiscardsTaskFutureSetCallbackFirst) {
598 // The callback object will get destroyed when the ManualExecutor runs out
599 // of scope.
600
601 // A promise will be captured by the callback lambda so we can observe that
602 // it will be destroyed.
603 Promise<Unit> captured_promise;
604 auto captured_promise_future = captured_promise.getFuture();
605
606 Optional<SemiFuture<int>> future;
607 {
608 ManualExecutor x;
609 Promise<Unit> trigger;
610 future =
611 trigger.getFuture()
612 .via(&x)
613 .thenValue([c = std::move(captured_promise)](auto&&) { return 42; })
614 .semi();
615 trigger.setValue();
616 x.clear();
617 }
618
619 EXPECT_THROW(std::move(*future).get(std::chrono::seconds(5)), BrokenPromise);
620 EXPECT_THROW(
621 std::move(captured_promise_future).get(std::chrono::seconds(5)),
622 BrokenPromise);
623 }
624
TEST(ViaFunc,liftsVoid)625 TEST(ViaFunc, liftsVoid) {
626 ManualExecutor x;
627 int count = 0;
628 Future<Unit> f = via(&x, [&] { count++; });
629
630 EXPECT_EQ(0, count);
631 x.run();
632 EXPECT_EQ(1, count);
633 }
634
TEST(ViaFunc,value)635 TEST(ViaFunc, value) {
636 ManualExecutor x;
637 EXPECT_EQ(42, via(&x, [] { return 42; }).getVia(&x));
638 }
639
TEST(ViaFunc,exception)640 TEST(ViaFunc, exception) {
641 ManualExecutor x;
642 EXPECT_THROW(
643 via(&x, []() -> int { throw std::runtime_error("expected"); }).getVia(&x),
644 std::runtime_error);
645 }
646
TEST(ViaFunc,future)647 TEST(ViaFunc, future) {
648 ManualExecutor x;
649 EXPECT_EQ(42, via(&x, [] { return makeFuture(42); }).getVia(&x));
650 }
651
TEST(ViaFunc,semi_future)652 TEST(ViaFunc, semi_future) {
653 ManualExecutor x;
654 EXPECT_EQ(42, via(&x, [] { return makeSemiFuture(42); }).getVia(&x));
655 }
656
TEST(ViaFunc,voidFuture)657 TEST(ViaFunc, voidFuture) {
658 ManualExecutor x;
659 int count = 0;
660 via(&x, [&] { count++; }).getVia(&x);
661 EXPECT_EQ(1, count);
662 }
663
TEST(ViaFunc,isSticky)664 TEST(ViaFunc, isSticky) {
665 ManualExecutor x;
666 int count = 0;
667
668 auto f = via(&x, [&] { count++; });
669 x.run();
670
671 std::move(f).thenValue([&](auto&&) { count++; });
672 EXPECT_EQ(1, count);
673 x.run();
674 EXPECT_EQ(2, count);
675 }
676
TEST(ViaFunc,moveOnly)677 TEST(ViaFunc, moveOnly) {
678 ManualExecutor x;
679 auto intp = std::make_unique<int>(42);
680
681 EXPECT_EQ(42, via(&x, [intp = std::move(intp)] { return *intp; }).getVia(&x));
682 }
683
TEST(ViaFunc,valueKeepAlive)684 TEST(ViaFunc, valueKeepAlive) {
685 ManualExecutor x;
686 EXPECT_EQ(42, via(getKeepAliveToken(&x), [] { return 42; }).getVia(&x));
687 }
688
TEST(ViaFunc,thenValueKeepAlive)689 TEST(ViaFunc, thenValueKeepAlive) {
690 ManualExecutor x;
691 EXPECT_EQ(
692 42,
693 via(getKeepAliveToken(&x))
694 .thenValue([](auto&&) { return 42; })
695 .getVia(&x));
696 }
697