feat: improved state store
This commit is contained in:
		
							parent
							
								
									6e5b35ea92
								
							
						
					
					
						commit
						6606e41dde
					
				
					 13 changed files with 405 additions and 251 deletions
				
			
		|  | @ -1,21 +1,29 @@ | |||
| use std::{collections::HashMap, convert::TryFrom, time::SystemTime}; | ||||
| use std::{ | ||||
|     collections::HashMap, | ||||
|     convert::TryFrom, | ||||
|     fmt::Debug, | ||||
|     sync::Arc, | ||||
|     time::{Duration, Instant, SystemTime}, | ||||
| }; | ||||
| 
 | ||||
| use crate::{appservice_server, server_server, utils, Error, PduEvent, Result}; | ||||
| use federation::transactions::send_transaction_message; | ||||
| use log::warn; | ||||
| use rocket::futures::stream::{FuturesUnordered, StreamExt}; | ||||
| use ruma::{ | ||||
|     api::{appservice, federation}, | ||||
|     api::{appservice, federation, OutgoingRequest}, | ||||
|     ServerName, | ||||
| }; | ||||
| use sled::IVec; | ||||
| use tokio::select; | ||||
| use tokio::sync::Semaphore; | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct Sending { | ||||
|     /// The state for a given state hash.
 | ||||
|     pub(super) servernamepduids: sled::Tree, // ServernamePduId = (+)ServerName + PduId
 | ||||
|     pub(super) servercurrentpdus: sled::Tree, // ServerCurrentPdus = (+)ServerName + PduId (pduid can be empty for reservation)
 | ||||
|     pub(super) maximum_requests: Arc<Semaphore>, | ||||
| } | ||||
| 
 | ||||
| impl Sending { | ||||
|  | @ -40,35 +48,7 @@ impl Sending { | |||
|             for (server, pdu, is_appservice) in servercurrentpdus | ||||
|                 .iter() | ||||
|                 .filter_map(|r| r.ok()) | ||||
|                 .map(|(key, _)| { | ||||
|                     let mut parts = key.splitn(2, |&b| b == 0xff); | ||||
|                     let server = parts.next().expect("splitn always returns one element"); | ||||
|                     let pdu = parts.next().ok_or_else(|| { | ||||
|                         Error::bad_database("Invalid bytes in servercurrentpdus.") | ||||
|                     })?; | ||||
| 
 | ||||
|                     let server = utils::string_from_bytes(&server).map_err(|_| { | ||||
|                         Error::bad_database("Invalid server bytes in server_currenttransaction") | ||||
|                     })?; | ||||
| 
 | ||||
|                     // Appservices start with a plus
 | ||||
|                     let (server, is_appservice) = if server.starts_with("+") { | ||||
|                         (&server[1..], true) | ||||
|                     } else { | ||||
|                         (&*server, false) | ||||
|                     }; | ||||
| 
 | ||||
|                     Ok::<_, Error>(( | ||||
|                         Box::<ServerName>::try_from(server).map_err(|_| { | ||||
|                             Error::bad_database( | ||||
|                                 "Invalid server string in server_currenttransaction", | ||||
|                             ) | ||||
|                         })?, | ||||
|                         IVec::from(pdu), | ||||
|                         is_appservice, | ||||
|                     )) | ||||
|                 }) | ||||
|                 .filter_map(|r| r.ok()) | ||||
|                 .filter_map(|(key, _)| Self::parse_servercurrentpdus(key).ok()) | ||||
|                 .filter(|(_, pdu, _)| !pdu.is_empty()) // Skip reservation key
 | ||||
|                 .take(50) | ||||
|             // This should not contain more than 50 anyway
 | ||||
|  | @ -90,6 +70,8 @@ impl Sending { | |||
|                 )); | ||||
|             } | ||||
| 
 | ||||
|             let mut last_failed_try: HashMap<Box<ServerName>, (u32, Instant)> = HashMap::new(); | ||||
| 
 | ||||
|             let mut subscriber = servernamepduids.watch_prefix(b""); | ||||
|             loop { | ||||
|                 select! { | ||||
|  | @ -140,9 +122,24 @@ impl Sending { | |||
|                                     // servercurrentpdus with the prefix should be empty now
 | ||||
|                                 } | ||||
|                             } | ||||
|                             Err((server, _is_appservice, e)) => { | ||||
|                                 warn!("Couldn't send transaction to {}: {}", server, e) | ||||
|                                 // TODO: exponential backoff
 | ||||
|                             Err((server, is_appservice, e)) => { | ||||
|                                 warn!("Couldn't send transaction to {}: {}", server, e); | ||||
|                                 let mut prefix = if is_appservice { | ||||
|                                     "+".as_bytes().to_vec() | ||||
|                                 } else { | ||||
|                                     Vec::new() | ||||
|                                 }; | ||||
|                                 prefix.extend_from_slice(server.as_bytes()); | ||||
|                                 prefix.push(0xff); | ||||
|                                 last_failed_try.insert(server.clone(), match last_failed_try.get(&server) { | ||||
|                                     Some(last_failed) => { | ||||
|                                         (last_failed.0+1, Instant::now()) | ||||
|                                     }, | ||||
|                                     None => { | ||||
|                                         (1, Instant::now()) | ||||
|                                     } | ||||
|                                 }); | ||||
|                                 servercurrentpdus.remove(&prefix).unwrap(); | ||||
|                             } | ||||
|                         }; | ||||
|                     }, | ||||
|  | @ -174,8 +171,19 @@ impl Sending { | |||
|                                     .ok() | ||||
|                                     .map(|pdu_id| (server, is_appservice, pdu_id)) | ||||
|                                 ) | ||||
|                                 // TODO: exponential backoff
 | ||||
|                                 .filter(|(server, is_appservice, _)| { | ||||
|                                     if last_failed_try.get(server).map_or(false, |(tries, instant)| { | ||||
|                                         // Fail if a request has failed recently (exponential backoff)
 | ||||
|                                         let mut min_elapsed_duration = Duration::from_secs(60) * *tries * *tries; | ||||
|                                         if min_elapsed_duration > Duration::from_secs(60*60*24) { | ||||
|                                             min_elapsed_duration = Duration::from_secs(60*60*24); | ||||
|                                         } | ||||
| 
 | ||||
|                                         instant.elapsed() < min_elapsed_duration | ||||
|                                     }) { | ||||
|                                         return false; | ||||
|                                     } | ||||
| 
 | ||||
|                                     let mut prefix = if *is_appservice { | ||||
|                                         "+".as_bytes().to_vec() | ||||
|                                     } else { | ||||
|  | @ -308,4 +316,63 @@ impl Sending { | |||
|             .map_err(|e| (server, is_appservice, e)) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn parse_servercurrentpdus(key: IVec) -> Result<(Box<ServerName>, IVec, bool)> { | ||||
|         let mut parts = key.splitn(2, |&b| b == 0xff); | ||||
|         let server = parts.next().expect("splitn always returns one element"); | ||||
|         let pdu = parts | ||||
|             .next() | ||||
|             .ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?; | ||||
| 
 | ||||
|         let server = utils::string_from_bytes(&server).map_err(|_| { | ||||
|             Error::bad_database("Invalid server bytes in server_currenttransaction") | ||||
|         })?; | ||||
| 
 | ||||
|         // Appservices start with a plus
 | ||||
|         let (server, is_appservice) = if server.starts_with("+") { | ||||
|             (&server[1..], true) | ||||
|         } else { | ||||
|             (&*server, false) | ||||
|         }; | ||||
| 
 | ||||
|         Ok::<_, Error>(( | ||||
|             Box::<ServerName>::try_from(server).map_err(|_| { | ||||
|                 Error::bad_database("Invalid server string in server_currenttransaction") | ||||
|             })?, | ||||
|             IVec::from(pdu), | ||||
|             is_appservice, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn send_federation_request<T: OutgoingRequest>( | ||||
|         &self, | ||||
|         globals: &crate::database::globals::Globals, | ||||
|         destination: Box<ServerName>, | ||||
|         request: T, | ||||
|     ) -> Result<T::IncomingResponse> | ||||
|     where | ||||
|         T: Debug, | ||||
|     { | ||||
|         let permit = self.maximum_requests.acquire().await; | ||||
|         let response = server_server::send_request(globals, destination, request).await; | ||||
|         drop(permit); | ||||
| 
 | ||||
|         response | ||||
|     } | ||||
| 
 | ||||
|     pub async fn send_appservice_request<T: OutgoingRequest>( | ||||
|         &self, | ||||
|         globals: &crate::database::globals::Globals, | ||||
|         registration: serde_yaml::Value, | ||||
|         request: T, | ||||
|     ) -> Result<T::IncomingResponse> | ||||
|     where | ||||
|         T: Debug, | ||||
|     { | ||||
|         let permit = self.maximum_requests.acquire().await; | ||||
|         let response = appservice_server::send_request(globals, registration, request).await; | ||||
|         drop(permit); | ||||
| 
 | ||||
|         response | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timo Kösters
						Timo Kösters