use parking_lot::Mutex;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use std::time::Instant;
use anyhow::Result;
use super::output_plugin_config_diagnostics;
use super::InitializedPlugin;
use super::Plugin;
use crate::environment::CanonicalizedPathBuf;
use crate::environment::Environment;
use crate::patterns::get_plugin_association_glob_matcher;
use crate::utils::get_lowercase_file_extension;
use crate::utils::get_lowercase_file_name;
use crate::utils::ErrorCountLogger;
use crate::utils::GlobMatcher;
pub struct PluginsDropper<TEnvironment: Environment> {
pools: Arc<PluginPools<TEnvironment>>,
}
impl<TEnvironment: Environment> Drop for PluginsDropper<TEnvironment> {
fn drop(&mut self) {
self.pools.drop_plugins();
}
}
impl<TEnvironment: Environment> PluginsDropper<TEnvironment> {
pub fn new(pools: Arc<PluginPools<TEnvironment>>) -> Self {
PluginsDropper { pools }
}
}
#[derive(Default)]
struct PluginNameResolutionMaps {
extension_to_plugin_name_map: HashMap<String, String>,
file_name_to_plugin_name_map: HashMap<String, String>,
association_matchers: Vec<(String, GlobMatcher)>,
}
pub struct PluginPools<TEnvironment: Environment> {
environment: TEnvironment,
pools: Mutex<HashMap<String, Arc<InitializedPluginPool<TEnvironment>>>>,
plugin_name_maps: RwLock<PluginNameResolutionMaps>,
#[allow(clippy::type_complexity)]
plugins_for_plugins: Mutex<HashMap<String, HashMap<String, Vec<Box<dyn InitializedPlugin>>>>>,
}
impl<TEnvironment: Environment> PluginPools<TEnvironment> {
pub fn new(environment: TEnvironment) -> Self {
PluginPools {
environment,
pools: Default::default(),
plugin_name_maps: Default::default(),
plugins_for_plugins: Default::default(),
}
}
pub fn drop_plugins(&self) {
{
let mut pools = self.pools.lock();
for pool in pools.values() {
pool.drop_plugins();
}
pools.clear();
}
{
let mut plugins_for_plugins = self.plugins_for_plugins.lock();
plugins_for_plugins.clear();
}
}
pub fn set_plugins(&self, plugins: Vec<Box<dyn Plugin>>, config_base_path: &CanonicalizedPathBuf) -> Result<()> {
let mut pools = self.pools.lock();
let mut plugin_name_maps: PluginNameResolutionMaps = Default::default();
for plugin in plugins {
let plugin_name = plugin.name().to_string();
let plugin_extensions = plugin.file_extensions().clone();
let plugin_file_names = plugin.file_names().clone();
for extension in plugin_extensions.iter() {
plugin_name_maps
.extension_to_plugin_name_map
.entry(extension.to_owned())
.or_insert_with(|| plugin_name.clone());
}
for file_name in plugin_file_names.iter() {
plugin_name_maps
.file_name_to_plugin_name_map
.entry(file_name.to_owned())
.or_insert_with(|| plugin_name.clone());
}
if let Some(matchers) = get_plugin_association_glob_matcher(&*plugin, config_base_path)? {
plugin_name_maps.association_matchers.push((plugin_name.clone(), matchers));
}
pools.insert(plugin_name.clone(), Arc::new(InitializedPluginPool::new(plugin, self.environment.clone())));
}
*self.plugin_name_maps.write() = plugin_name_maps;
Ok(())
}
pub fn get_pool(&self, plugin_name: &str) -> Option<Arc<InitializedPluginPool<TEnvironment>>> {
self.pools.lock().get(plugin_name).cloned()
}
pub fn take_instance_for_plugin(&self, parent_plugin_name: &str, sub_plugin_name: &str) -> Result<Box<dyn InitializedPlugin>> {
let plugin = self.with_plugins_for_parent_and_sub_plugin(parent_plugin_name, sub_plugin_name, |plugins| plugins.pop());
if let Some(plugin) = plugin {
Ok(plugin)
} else {
let pool = self.get_pool(sub_plugin_name).expect("Expected the plugin to exist in the pool.");
if let Some(plugin) = pool.take_if_available() {
Ok(plugin)
} else {
pool.create_instance()
}
}
}
pub fn release_instance_for_plugin(&self, parent_plugin_name: &str, sub_plugin_name: &str, plugin: Box<dyn InitializedPlugin>) {
self.with_plugins_for_parent_and_sub_plugin(parent_plugin_name, sub_plugin_name, |plugins| {
plugins.push(plugin);
});
}
fn with_plugins_for_parent_and_sub_plugin<TResult>(
&self,
parent_plugin_name: &str,
sub_plugin_name: &str,
with_plugins: impl FnOnce(&mut Vec<Box<dyn InitializedPlugin>>) -> TResult,
) -> TResult {
let mut plugins_for_plugins = self.plugins_for_plugins.lock();
let plugins_for_plugin = if let Some(plugins_for_plugin) = plugins_for_plugins.get_mut(parent_plugin_name) {
plugins_for_plugin
} else {
plugins_for_plugins.insert(parent_plugin_name.to_string(), HashMap::new());
plugins_for_plugins.get_mut(parent_plugin_name).unwrap()
};
let plugins = if let Some(plugins) = plugins_for_plugin.get_mut(sub_plugin_name) {
plugins
} else {
plugins_for_plugin.insert(sub_plugin_name.to_string(), Vec::new());
plugins_for_plugin.get_mut(sub_plugin_name).unwrap()
};
with_plugins(plugins)
}
pub fn get_plugin_names_from_file_name(&self, file_name: &Path) -> Vec<String> {
let mut plugin_names = Vec::new();
let plugin_name_maps = self.plugin_name_maps.read();
if let Some(file_name) = get_lowercase_file_name(file_name) {
for (plugin_name, matcher) in plugin_name_maps.association_matchers.iter() {
if matcher.is_match(&file_name) {
plugin_names.push(plugin_name.to_owned());
}
}
if !plugin_names.is_empty() {
return plugin_names;
}
if let Some(plugin_name) = plugin_name_maps.file_name_to_plugin_name_map.get(&file_name) {
plugin_names.push(plugin_name.to_owned());
return plugin_names;
}
}
if let Some(ext) = get_lowercase_file_extension(file_name) {
if let Some(plugin_name) = plugin_name_maps.extension_to_plugin_name_map.get(&ext) {
plugin_names.push(plugin_name.to_owned());
}
}
plugin_names
}
pub fn release(&self, parent_plugin_name: &str) {
let plugins_for_plugin = self.plugins_for_plugins.lock().remove(parent_plugin_name);
if let Some(plugins_for_plugin) = plugins_for_plugin {
for (sub_plugin_name, initialized_plugins) in plugins_for_plugin.into_iter() {
if let Some(pool) = self.get_pool(&sub_plugin_name) {
pool.release_all(initialized_plugins);
}
}
}
}
pub fn get_plugins_hash(&self) -> u64 {
use std::num::Wrapping;
let pools = self.pools.lock();
let mut hash_sum = Wrapping(0);
for (_, pool) in pools.iter() {
hash_sum += Wrapping(pool.plugin.get_hash());
}
hash_sum.0
}
}
pub struct PoolTimeSnapshot {
pub startup_time: u64,
pub average_format_time: u64,
pub has_plugin_available: bool,
}
struct PluginTimeStats {
startup_time: u64,
total_format_time: u64,
format_count: u64,
}
pub struct OptionalPluginAndPool<TEnvironment: Environment> {
pub plugin: Option<Box<dyn InitializedPlugin>>,
pub pool: Arc<InitializedPluginPool<TEnvironment>>,
}
impl<TEnvironment: Environment> OptionalPluginAndPool<TEnvironment> {
pub fn from_pool(pool: Arc<InitializedPluginPool<TEnvironment>>) -> Self {
Self { plugin: None, pool }
}
pub fn release_plugin(self) {
if let Some(plugin) = self.plugin {
self.pool.release(plugin);
}
}
}
pub struct PluginAndPoolMutRef<'a, TEnvironment: Environment> {
pub plugin: &'a mut Box<dyn InitializedPlugin>,
pub pool: &'a Arc<InitializedPluginPool<TEnvironment>>,
}
pub enum TakePluginResult {
HadDiagnostics,
Success(Box<dyn InitializedPlugin>),
}
pub struct InitializedPluginPool<TEnvironment: Environment> {
environment: TEnvironment,
name: String,
plugin: Box<dyn Plugin>,
items: Mutex<Vec<Box<dyn InitializedPlugin>>>, time_stats: RwLock<PluginTimeStats>,
checked_diagnostics: Mutex<Option<bool>>,
}
impl<TEnvironment: Environment> InitializedPluginPool<TEnvironment> {
pub fn new(plugin: Box<dyn Plugin>, environment: TEnvironment) -> InitializedPluginPool<TEnvironment> {
InitializedPluginPool {
environment,
name: plugin.name().to_string(),
plugin,
items: Mutex::new(Vec::new()),
time_stats: RwLock::new(PluginTimeStats {
startup_time: 250,
total_format_time: 50,
format_count: 1,
}),
checked_diagnostics: Mutex::new(None),
}
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn drop_plugins(&self) {
let mut items = self.items.lock();
items.clear();
}
pub fn take_or_create_checking_config_diagnostics(&self, error_logger: &ErrorCountLogger<TEnvironment>) -> Result<TakePluginResult> {
if let Some(plugin) = self.take_if_available() {
Ok(TakePluginResult::Success(plugin))
} else {
let instance = self.create_instance()?;
let mut has_checked_diagnostics = self.checked_diagnostics.lock();
match *has_checked_diagnostics {
Some(was_success) => {
if !was_success {
return Ok(TakePluginResult::HadDiagnostics);
}
}
None => {
let result = output_plugin_config_diagnostics(self.name(), &*instance, error_logger);
*has_checked_diagnostics = Some(result.is_ok());
if let Err(err) = result {
self.environment.log_stderr(&err.to_string());
return Ok(TakePluginResult::HadDiagnostics);
}
}
}
Ok(TakePluginResult::Success(instance))
}
}
pub fn take_if_available(&self) -> Option<Box<dyn InitializedPlugin>> {
let mut items = self.items.lock();
items.pop()
}
pub fn release(&self, plugin: Box<dyn InitializedPlugin>) {
let mut items = self.items.lock();
items.push(plugin);
}
pub fn release_all(&self, plugins: Vec<Box<dyn InitializedPlugin>>) {
let mut items = self.items.lock();
items.extend(plugins);
}
pub fn get_time_snapshot(&self) -> PoolTimeSnapshot {
let has_plugin_available = !self.items.lock().is_empty();
let time_stats = self.time_stats.read();
let average_format_time = (time_stats.total_format_time as f64 / time_stats.format_count as f64) as u64;
PoolTimeSnapshot {
startup_time: time_stats.startup_time,
average_format_time,
has_plugin_available,
}
}
fn create_instance(&self) -> Result<Box<dyn InitializedPlugin>> {
let start_instant = Instant::now();
log_verbose!(self.environment, "Creating instance of {}", self.plugin.name());
let plugin = self.plugin.initialize()?;
let startup_duration = start_instant.elapsed().as_millis() as u64;
log_verbose!(self.environment, "Created instance of {} in {}ms", self.plugin.name(), startup_duration);
self.time_stats.write().startup_time = startup_duration; Ok(plugin)
}
pub fn format_measuring_time<TResult>(&self, mut action: impl FnMut() -> TResult) -> TResult {
let start_instant = Instant::now();
let result = action();
let elapsed_time = start_instant.elapsed();
let mut time_stats = self.time_stats.write();
time_stats.total_format_time += elapsed_time.as_millis() as u64;
time_stats.format_count += 1;
result
}
}