@@ -6,6 +6,7 @@ use std::fmt::Debug;
6
6
use std:: fmt:: Display ;
7
7
use std:: fmt:: Formatter ;
8
8
use std:: marker:: PhantomData ;
9
+ use std:: num:: NonZeroUsize ;
9
10
use std:: sync:: Arc ;
10
11
11
12
use serde:: de:: DeserializeOwned ;
@@ -14,6 +15,7 @@ use serde::Serialize;
14
15
use thiserror:: Error ;
15
16
16
17
use crate :: introspect:: IntrospectionResponse ;
18
+ use crate :: pool:: JsWorkerPool ;
17
19
use crate :: worker:: JsWorker ;
18
20
19
21
// ------------------------------------
@@ -398,6 +400,235 @@ where
398
400
}
399
401
}
400
402
403
+ /// A Deno worker backed query Planner,
404
+ /// using a pool of JsRuntimes load balanced
405
+ /// using Power of Two Choices.
406
+ pub struct PooledPlanner < T >
407
+ where
408
+ T : DeserializeOwned + Send + Debug + ' static ,
409
+ {
410
+ pool : Arc < JsWorkerPool > ,
411
+ schema_id : u64 ,
412
+ t : PhantomData < T > ,
413
+ }
414
+
415
+ impl < T > Debug for PooledPlanner < T >
416
+ where
417
+ T : DeserializeOwned + Send + Debug + ' static ,
418
+ {
419
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
420
+ f. debug_struct ( "PooledPlanner" )
421
+ . field ( "schema_id" , & self . schema_id )
422
+ . finish ( )
423
+ }
424
+ }
425
+
426
+ impl < T > PooledPlanner < T >
427
+ where
428
+ T : DeserializeOwned + Send + Debug + ' static ,
429
+ {
430
+ /// Instantiate a `Planner` from a schema string
431
+ pub async fn new (
432
+ schema : String ,
433
+ config : QueryPlannerConfig ,
434
+ pool_size : NonZeroUsize ,
435
+ ) -> Result < Self , Vec < PlannerError > > {
436
+ let schema_id: u64 = rand:: random ( ) ;
437
+
438
+ let pool = JsWorkerPool :: new ( include_str ! ( "../bundled/plan_worker.js" ) , pool_size) ;
439
+
440
+ let workers_are_setup = pool
441
+ . broadcast_request :: < PlanCmd , BridgeSetupResult < serde_json:: Value > > (
442
+ PlanCmd :: UpdateSchema {
443
+ schema,
444
+ config,
445
+ schema_id,
446
+ } ,
447
+ )
448
+ . await
449
+ . map_err ( |e| {
450
+ vec ! [ WorkerError {
451
+ name: Some ( "planner setup error" . to_string( ) ) ,
452
+ message: Some ( e. to_string( ) ) ,
453
+ stack: None ,
454
+ extensions: None ,
455
+ locations: Default :: default ( ) ,
456
+ }
457
+ . into( ) ]
458
+ } ) ;
459
+
460
+ // Both cases below the mean schema update failed.
461
+ // We need to pay attention here.
462
+ // returning early will drop the worker, which will join the jsruntime thread.
463
+ // however the event loop will run for ever. We need to let the worker know it needs to exit,
464
+ // before we drop the worker
465
+ match workers_are_setup {
466
+ Err ( setup_error) => {
467
+ let _ = pool
468
+ . broadcast_request :: < PlanCmd , serde_json:: Value > ( PlanCmd :: Exit { schema_id } )
469
+ . await ;
470
+ return Err ( setup_error) ;
471
+ }
472
+ Ok ( responses) => {
473
+ for r in responses {
474
+ if let Some ( error) = r. errors {
475
+ let _ = pool. broadcast_send ( None , PlanCmd :: Exit { schema_id } ) . await ;
476
+ return Err ( error) ;
477
+ }
478
+ }
479
+ }
480
+ }
481
+
482
+ let pool = Arc :: new ( pool) ;
483
+
484
+ Ok ( Self {
485
+ pool,
486
+ schema_id,
487
+ t : PhantomData ,
488
+ } )
489
+ }
490
+
491
+ /// Update `Planner` from a schema string
492
+ pub async fn update (
493
+ & self ,
494
+ schema : String ,
495
+ config : QueryPlannerConfig ,
496
+ ) -> Result < Self , Vec < PlannerError > > {
497
+ let schema_id: u64 = rand:: random ( ) ;
498
+
499
+ let workers_are_setup = self
500
+ . pool
501
+ . broadcast_request :: < PlanCmd , BridgeSetupResult < serde_json:: Value > > (
502
+ PlanCmd :: UpdateSchema {
503
+ schema,
504
+ config,
505
+ schema_id,
506
+ } ,
507
+ )
508
+ . await
509
+ . map_err ( |e| {
510
+ vec ! [ WorkerError {
511
+ name: Some ( "planner setup error" . to_string( ) ) ,
512
+ message: Some ( e. to_string( ) ) ,
513
+ stack: None ,
514
+ extensions: None ,
515
+ locations: Default :: default ( ) ,
516
+ }
517
+ . into( ) ]
518
+ } ) ;
519
+
520
+ // If the update failed, we keep the existing schema in place
521
+ match workers_are_setup {
522
+ Err ( setup_error) => {
523
+ return Err ( setup_error) ;
524
+ }
525
+ Ok ( responses) => {
526
+ for r in responses {
527
+ if let Some ( error) = r. errors {
528
+ let _ = self
529
+ . pool
530
+ . broadcast_send ( None , PlanCmd :: Exit { schema_id } )
531
+ . await ;
532
+ return Err ( error) ;
533
+ }
534
+ }
535
+ }
536
+ }
537
+
538
+ Ok ( Self {
539
+ pool : self . pool . clone ( ) ,
540
+ schema_id,
541
+ t : PhantomData ,
542
+ } )
543
+ }
544
+
545
+ /// Plan a query against an instantiated query planner
546
+ pub async fn plan (
547
+ & self ,
548
+ query : String ,
549
+ operation_name : Option < String > ,
550
+ options : PlanOptions ,
551
+ ) -> Result < PlanResult < T > , crate :: error:: Error > {
552
+ self . pool
553
+ . request ( PlanCmd :: Plan {
554
+ query,
555
+ operation_name,
556
+ schema_id : self . schema_id ,
557
+ options,
558
+ } )
559
+ . await
560
+ }
561
+
562
+ /// Generate the API schema from the current schema
563
+ pub async fn api_schema ( & self ) -> Result < ApiSchema , crate :: error:: Error > {
564
+ self . pool
565
+ . request ( PlanCmd :: ApiSchema {
566
+ schema_id : self . schema_id ,
567
+ } )
568
+ . await
569
+ }
570
+
571
+ /// Generate the introspection response for this query
572
+ pub async fn introspect (
573
+ & self ,
574
+ query : String ,
575
+ ) -> Result < IntrospectionResponse , crate :: error:: Error > {
576
+ self . pool
577
+ . request ( PlanCmd :: Introspect {
578
+ query,
579
+ schema_id : self . schema_id ,
580
+ } )
581
+ . await
582
+ }
583
+
584
+ /// Get the operation signature for a query
585
+ pub async fn operation_signature (
586
+ & self ,
587
+ query : String ,
588
+ operation_name : Option < String > ,
589
+ ) -> Result < String , crate :: error:: Error > {
590
+ self . pool
591
+ . request ( PlanCmd :: Signature {
592
+ query,
593
+ operation_name,
594
+ schema_id : self . schema_id ,
595
+ } )
596
+ . await
597
+ }
598
+
599
+ /// Extract the subgraph schemas from the supergraph schema
600
+ pub async fn subgraphs ( & self ) -> Result < HashMap < String , String > , crate :: error:: Error > {
601
+ self . pool
602
+ . request ( PlanCmd :: Subgraphs {
603
+ schema_id : self . schema_id ,
604
+ } )
605
+ . await
606
+ }
607
+ }
608
+
609
+ impl < T > Drop for PooledPlanner < T >
610
+ where
611
+ T : DeserializeOwned + Send + Debug + ' static ,
612
+ {
613
+ fn drop ( & mut self ) {
614
+ // Send a PlanCmd::Exit signal
615
+ let pool_clone = self . pool . clone ( ) ;
616
+ let schema_id = self . schema_id ;
617
+ let _ = std:: thread:: spawn ( move || {
618
+ let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
619
+ . build ( )
620
+ . unwrap ( ) ;
621
+
622
+ let _ = runtime. block_on ( async move {
623
+ pool_clone
624
+ . broadcast_send ( None , PlanCmd :: Exit { schema_id } )
625
+ . await
626
+ } ) ;
627
+ } )
628
+ . join ( ) ;
629
+ }
630
+ }
631
+
401
632
/// A Deno worker backed query Planner.
402
633
403
634
pub struct Planner < T >
0 commit comments