2
2
3
3
mod spin;
4
4
5
- use std:: collections:: HashMap ;
6
-
7
5
use anyhow:: { anyhow, Context , Result } ;
8
6
use futures:: { future:: join_all, StreamExt } ;
9
7
use redis:: { Client , ConnectionLike } ;
10
8
use serde:: { de:: IgnoredAny , Deserialize , Serialize } ;
11
9
use spin_core:: async_trait;
12
10
use spin_trigger:: { cli:: NoArgs , TriggerAppEngine , TriggerExecutor } ;
11
+ use std:: collections:: HashMap ;
12
+ use std:: sync:: Arc ;
13
13
14
14
use crate :: spin:: SpinRedisExecutor ;
15
15
16
16
pub ( crate ) type RuntimeData = ( ) ;
17
17
pub ( crate ) type Store = spin_core:: Store < RuntimeData > ;
18
18
19
+ type ChannelComponents = HashMap < String , Vec < String > > ;
19
20
/// The Spin Redis trigger.
21
+ #[ derive( Clone ) ]
20
22
pub struct RedisTrigger {
21
- engine : TriggerAppEngine < Self > ,
22
- // Redis address to connect to
23
- address : String ,
24
- // Mapping of subscription channels to component IDs
25
- channel_components : HashMap < String , Vec < String > > ,
23
+ engine : Arc < TriggerAppEngine < Self > > ,
24
+ // Mapping of server url with subscription channel and associated component IDs
25
+ server_channels : HashMap < String , ChannelComponents > ,
26
26
}
27
27
28
28
/// Redis trigger configuration.
@@ -33,6 +33,8 @@ pub struct RedisTriggerConfig {
33
33
pub component : String ,
34
34
/// Channel to subscribe to
35
35
pub channel : String ,
36
+ /// optional overide address for trigger
37
+ pub address : Option < String > ,
36
38
/// Trigger executor (currently unused)
37
39
#[ serde( default , skip_serializing) ]
38
40
pub executor : IgnoredAny ,
@@ -52,32 +54,90 @@ impl TriggerExecutor for RedisTrigger {
52
54
type RunConfig = NoArgs ;
53
55
54
56
async fn new ( engine : TriggerAppEngine < Self > ) -> Result < Self > {
55
- let address = engine
57
+ let default_address : String = engine
56
58
. trigger_metadata :: < TriggerMetadata > ( ) ?
57
59
. unwrap_or_default ( )
58
60
. address ;
59
- let address_expr = spin_expressions:: Template :: new ( address ) ?;
60
- let address = engine. resolve_template ( & address_expr ) ?;
61
+ let default_address_expr = spin_expressions:: Template :: new ( default_address ) ?;
62
+ let default_address = engine. resolve_template ( & default_address_expr ) ?;
61
63
62
- let mut channel_components : HashMap < String , Vec < String > > = HashMap :: new ( ) ;
64
+ let mut server_channels : HashMap < String , ChannelComponents > = HashMap :: new ( ) ;
63
65
64
66
for ( _, config) in engine. trigger_configs ( ) {
65
- channel_components
67
+ let address = config. address . clone ( ) . unwrap_or ( default_address. clone ( ) ) ;
68
+ let address_expr = spin_expressions:: Template :: new ( address) ?;
69
+ let address = engine. resolve_template ( & address_expr) ?;
70
+ let server = server_channels. entry ( address) . or_default ( ) ;
71
+ server
66
72
. entry ( config. channel . clone ( ) )
67
73
. or_default ( )
68
74
. push ( config. component . clone ( ) ) ;
69
75
}
70
76
Ok ( Self {
71
- engine,
72
- address,
73
- channel_components,
77
+ engine : Arc :: new ( engine) ,
78
+ server_channels,
74
79
} )
75
80
}
76
81
77
82
/// Run the Redis trigger indefinitely.
78
83
async fn run ( self , _config : Self :: RunConfig ) -> Result < ( ) > {
79
- let address = & self . address ;
84
+ let tasks: Vec < _ > = self
85
+ . server_channels
86
+ . clone ( )
87
+ . into_iter ( )
88
+ . map ( |( server_address, channel_components) | {
89
+ let trigger = self . clone ( ) ;
90
+ tokio:: spawn ( async move {
91
+ trigger
92
+ . run_listener ( server_address. clone ( ) , channel_components. clone ( ) )
93
+ . await
94
+ } )
95
+ } )
96
+ . collect ( ) ;
97
+
98
+ // wait for the first handle to be returned and drop the rest
99
+ let ( _, _, rest) = futures:: future:: select_all ( tasks) . await ;
100
+ drop ( rest) ;
101
+
102
+ Ok ( ( ) )
103
+ }
104
+ }
80
105
106
+ impl RedisTrigger {
107
+ // Handle the message.
108
+ async fn handle (
109
+ & self ,
110
+ address : & str ,
111
+ channel_components : & ChannelComponents ,
112
+ msg : redis:: Msg ,
113
+ ) -> Result < ( ) > {
114
+ let channel = msg. get_channel_name ( ) ;
115
+ tracing:: info!( "Received message on channel {address}:{:?}" , channel) ;
116
+
117
+ if let Some ( component_ids) = channel_components. get ( channel) {
118
+ let futures = component_ids. iter ( ) . map ( |id| {
119
+ tracing:: trace!( "Executing Redis component {id:?}" ) ;
120
+ SpinRedisExecutor . execute ( & self . engine , id, channel, msg. get_payload_bytes ( ) )
121
+ } ) ;
122
+ let results: Vec < _ > = join_all ( futures) . await . into_iter ( ) . collect ( ) ;
123
+ let errors = results
124
+ . into_iter ( )
125
+ . filter_map ( |r| r. err ( ) )
126
+ . collect :: < Vec < _ > > ( ) ;
127
+ if !errors. is_empty ( ) {
128
+ return Err ( anyhow ! ( "{errors:#?}" ) ) ;
129
+ }
130
+ } else {
131
+ tracing:: debug!( "No subscription found for {:?}" , channel) ;
132
+ }
133
+ Ok ( ( ) )
134
+ }
135
+
136
+ async fn run_listener (
137
+ & self ,
138
+ address : String ,
139
+ channel_components : ChannelComponents ,
140
+ ) -> Result < ( ) > {
81
141
tracing:: info!( "Connecting to Redis server at {}" , address) ;
82
142
let mut client = Client :: open ( address. to_string ( ) ) ?;
83
143
let mut pubsub = client
@@ -88,55 +148,30 @@ impl TriggerExecutor for RedisTrigger {
88
148
89
149
println ! ( "Active Channels on {address}:" ) ;
90
150
// Subscribe to channels
91
- for ( channel, component) in self . channel_components . iter ( ) {
151
+ for ( channel, component) in channel_components. iter ( ) {
92
152
tracing:: info!( "Subscribing component {component:?} to channel {channel:?}" ) ;
93
153
pubsub. subscribe ( channel) . await ?;
94
- println ! ( "\t {channel}: [{}]" , component. join( "," ) ) ;
154
+ println ! ( "\t {address}:{ channel}: [{}]" , component. join( "," ) ) ;
95
155
}
96
156
97
157
let mut stream = pubsub. on_message ( ) ;
98
158
loop {
99
159
match stream. next ( ) . await {
100
160
Some ( msg) => {
101
- if let Err ( err) = self . handle ( msg) . await {
161
+ if let Err ( err) = self . handle ( & address , & channel_components , msg) . await {
102
162
tracing:: warn!( "Error handling message: {err}" ) ;
103
163
}
104
164
}
105
165
None => {
106
166
tracing:: trace!( "Empty message" ) ;
107
167
if !client. check_connection ( ) {
108
168
tracing:: info!( "No Redis connection available" ) ;
109
- break Ok ( ( ) ) ;
169
+ println ! ( "Disconnected from {address}" ) ;
170
+ break ;
110
171
}
111
172
}
112
173
} ;
113
174
}
114
- }
115
- }
116
-
117
- impl RedisTrigger {
118
- // Handle the message.
119
- async fn handle ( & self , msg : redis:: Msg ) -> Result < ( ) > {
120
- let channel = msg. get_channel_name ( ) ;
121
- tracing:: info!( "Received message on channel {:?}" , channel) ;
122
-
123
- if let Some ( component_ids) = self . channel_components . get ( channel) {
124
- let futures = component_ids. iter ( ) . map ( |id| {
125
- tracing:: trace!( "Executing Redis component {id:?}" ) ;
126
- SpinRedisExecutor . execute ( & self . engine , id, channel, msg. get_payload_bytes ( ) )
127
- } ) ;
128
- let results: Vec < _ > = join_all ( futures) . await . into_iter ( ) . collect ( ) ;
129
- let errors = results
130
- . into_iter ( )
131
- . filter_map ( |r| r. err ( ) )
132
- . collect :: < Vec < _ > > ( ) ;
133
- if !errors. is_empty ( ) {
134
- return Err ( anyhow ! ( "{errors:#?}" ) ) ;
135
- }
136
- } else {
137
- tracing:: debug!( "No subscription found for {:?}" , channel) ;
138
- }
139
-
140
175
Ok ( ( ) )
141
176
}
142
177
}
0 commit comments