@@ -314,6 +314,10 @@ fn start_geyser_runloop(
314314
315315 let mut surfpool_plugin_manager: Vec < Box < dyn GeyserPlugin > > = vec ! [ ] ;
316316
317+ // Map between each plugin's UUID to its position (index) in the surfpool_plugin_manager Vec.
318+ // Allows for easier reload/unload
319+ let mut plugin_uuid_map: HashMap < crate :: Uuid , usize > = HashMap :: new ( ) ;
320+
317321 #[ cfg( feature = "geyser_plugin" ) ]
318322 for plugin_config_path in plugin_config_paths. into_iter ( ) {
319323 let plugin_manifest_location = FileLocation :: from_path ( plugin_config_path) ;
@@ -367,6 +371,92 @@ fn start_geyser_runloop(
367371
368372 let ipc_router = RouterProxy :: new ( ) ;
369373
374+ // Helper function to load a subgraph plugin
375+ #[ cfg( feature = "subgraph" ) ]
376+ let load_subgraph_plugin = |uuid : uuid:: Uuid ,
377+ config : txtx_addon_network_svm_types:: subgraph:: PluginConfig ,
378+ surfpool_plugin_manager : & mut Vec < Box < dyn GeyserPlugin > > ,
379+ plugin_uuid_map : & mut HashMap < uuid:: Uuid , usize > ,
380+ indexing_enabled : & mut bool |
381+ -> Result < String , String > {
382+ let _ = subgraph_commands_tx. send ( SubgraphCommand :: CreateCollection (
383+ uuid,
384+ config. data . clone ( ) ,
385+ crossbeam_channel:: bounded ( 0 ) . 0 , // Temporary sender, will be replaced
386+ ) ) ;
387+ let mut plugin = SurfpoolSubgraphPlugin :: default ( ) ;
388+
389+ let ( server, ipc_token) =
390+ IpcOneShotServer :: < IpcReceiver < DataIndexingCommand > > :: new ( )
391+ . expect ( "Failed to create IPC one-shot server." ) ;
392+ let subgraph_plugin_config = SubgraphPluginConfig {
393+ uuid,
394+ ipc_token,
395+ subgraph_request : config. data . clone ( ) ,
396+ } ;
397+
398+ let config_file = serde_json:: to_string ( & subgraph_plugin_config)
399+ . map_err ( |e| format ! ( "Failed to serialize subgraph plugin config: {:?}" , e) ) ?;
400+
401+ plugin
402+ . on_load ( & config_file, false )
403+ . map_err ( |e| format ! ( "Failed to load Geyser plugin: {:?}" , e) ) ?;
404+
405+ if let Ok ( ( _, rx) ) = server. accept ( ) {
406+ let subgraph_rx = ipc_router
407+ . route_ipc_receiver_to_new_crossbeam_receiver :: < DataIndexingCommand > ( rx) ;
408+ let _ = subgraph_commands_tx. send ( SubgraphCommand :: ObserveCollection ( subgraph_rx) ) ;
409+ } ;
410+
411+ * indexing_enabled = true ;
412+
413+ let plugin: Box < dyn GeyserPlugin > = Box :: new ( plugin) ;
414+ let plugin_index = surfpool_plugin_manager. len ( ) ;
415+ surfpool_plugin_manager. push ( plugin) ;
416+ plugin_uuid_map. insert ( uuid, plugin_index) ;
417+
418+ let _ = simnet_events_tx. send ( SimnetEvent :: PluginLoaded ( "surfpool-subgraph" . into ( ) ) ) ;
419+ Ok ( format ! ( "http://localhost:8899/subgraph/{}" , uuid) ) // Return endpoint URL
420+ } ;
421+
422+ // Helper function to unload a plugin by UUID
423+ #[ cfg( feature = "subgraph" ) ]
424+ let unload_plugin_by_uuid = |uuid : uuid:: Uuid ,
425+ surfpool_plugin_manager : & mut Vec < Box < dyn GeyserPlugin > > ,
426+ plugin_uuid_map : & mut HashMap < uuid:: Uuid , usize > ,
427+ indexing_enabled : & mut bool |
428+ -> Result < ( ) , String > {
429+ let plugin_index = * plugin_uuid_map
430+ . get ( & uuid)
431+ . ok_or_else ( || format ! ( "Plugin {} not found" , uuid) ) ?;
432+
433+ if plugin_index >= surfpool_plugin_manager. len ( ) {
434+ return Err ( format ! ( "Plugin index {} out of bounds" , plugin_index) ) ;
435+ }
436+
437+ // Call on_unload before removing
438+ surfpool_plugin_manager[ plugin_index] . on_unload ( ) ;
439+
440+ // Remove the plugin from the list
441+ surfpool_plugin_manager. remove ( plugin_index) ;
442+ plugin_uuid_map. remove ( & uuid) ;
443+
444+ // Update all UUIDs that had indices after the removed one
445+ for ( _, idx) in plugin_uuid_map. iter_mut ( ) {
446+ if * idx > plugin_index {
447+ * idx -= 1 ;
448+ }
449+ }
450+
451+ // Check if we should disable indexing
452+ if surfpool_plugin_manager. is_empty ( ) {
453+ * indexing_enabled = false ;
454+ }
455+
456+ let _ = simnet_events_tx. send ( SimnetEvent :: info ( format ! ( "Plugin {} unloaded" , uuid) ) ) ;
457+ Ok ( ( ) )
458+ } ;
459+
370460 let err = loop {
371461 use agave_geyser_plugin_interface:: geyser_plugin_interface:: { ReplicaAccountInfoV3 , ReplicaAccountInfoVersions } ;
372462
@@ -383,37 +473,46 @@ fn start_geyser_runloop(
383473 }
384474 #[ cfg( feature = "subgraph" ) ]
385475 PluginManagerCommand :: LoadConfig ( uuid, config, notifier) => {
386- let _ = subgraph_commands_tx. send( SubgraphCommand :: CreateCollection ( uuid, config. data. clone( ) , notifier) ) ;
387- let mut plugin = SurfpoolSubgraphPlugin :: default ( ) ;
388-
389- let ( server, ipc_token) = IpcOneShotServer :: <IpcReceiver <DataIndexingCommand >>:: new( ) . expect( "Failed to create IPC one-shot server." ) ;
390- let subgraph_plugin_config = SubgraphPluginConfig {
391- uuid,
392- ipc_token,
393- subgraph_request: config. data. clone( )
394- } ;
395-
396- let config_file = match serde_json:: to_string( & subgraph_plugin_config) {
397- Ok ( c) => c,
476+ match load_subgraph_plugin( uuid, config, & mut surfpool_plugin_manager, & mut plugin_uuid_map, & mut indexing_enabled) {
477+ Ok ( endpoint_url) => {
478+ let _ = notifier. send( endpoint_url) ;
479+ }
398480 Err ( e) => {
399- let _ = simnet_events_tx. send( SimnetEvent :: error( format!( "Failed to serialize subgraph plugin config: {:?}" , e) ) ) ;
400- continue ;
481+ let _ = simnet_events_tx. send( SimnetEvent :: error( format!( "Failed to load plugin: {}" , e) ) ) ;
401482 }
402- } ;
403-
404- if let Err ( e) = plugin. on_load( & config_file, false ) {
405- let _ = simnet_events_tx. send( SimnetEvent :: error( format!( "Failed to load Geyser plugin: {:?}" , e) ) ) ;
406- } ;
407- if let Ok ( ( _, rx) ) = server. accept( ) {
408- let subgraph_rx = ipc_router. route_ipc_receiver_to_new_crossbeam_receiver:: <DataIndexingCommand >( rx) ;
409- let _ = subgraph_commands_tx. send( SubgraphCommand :: ObserveCollection ( subgraph_rx) ) ;
410- } ;
411-
412- indexing_enabled = true ;
483+ }
484+ }
485+ #[ cfg( not( feature = "subgraph" ) ) ]
486+ PluginManagerCommand :: UnloadPlugin ( _, _) => {
487+ continue ;
488+ }
489+ #[ cfg( feature = "subgraph" ) ]
490+ PluginManagerCommand :: UnloadPlugin ( uuid, notifier) => {
491+ let result = unload_plugin_by_uuid( uuid, & mut surfpool_plugin_manager, & mut plugin_uuid_map, & mut indexing_enabled) ;
492+ let _ = notifier. send( result) ;
493+ }
494+ #[ cfg( not( feature = "subgraph" ) ) ]
495+ PluginManagerCommand :: ReloadPlugin ( _, _, _) => {
496+ continue ;
497+ }
498+ #[ cfg( feature = "subgraph" ) ]
499+ PluginManagerCommand :: ReloadPlugin ( uuid, config, notifier) => {
500+ // First, unload the old plugin
501+ if let Err ( e) = unload_plugin_by_uuid( uuid, & mut surfpool_plugin_manager, & mut plugin_uuid_map, & mut indexing_enabled) {
502+ let _ = notifier. send( Err ( e) ) ;
503+ continue ;
504+ }
413505
414- let plugin: Box <dyn GeyserPlugin > = Box :: new( plugin) ;
415- surfpool_plugin_manager. push( plugin) ;
416- let _ = simnet_events_tx. send( SimnetEvent :: PluginLoaded ( "surfpool-subgraph" . into( ) ) ) ;
506+ // Then, load the new plugin with the same UUID
507+ match load_subgraph_plugin( uuid, config, & mut surfpool_plugin_manager, & mut plugin_uuid_map, & mut indexing_enabled) {
508+ Ok ( endpoint_url) => {
509+ let _ = notifier. send( Ok ( endpoint_url) ) ;
510+ let _ = simnet_events_tx. send( SimnetEvent :: info( format!( "Plugin {} reloaded" , uuid) ) ) ;
511+ }
512+ Err ( e) => {
513+ let _ = notifier. send( Err ( e) ) ;
514+ }
515+ }
417516 }
418517 }
419518 } ,
0 commit comments