Durante alcuni giorni di luglio, avevo scritto un servizio managed (lo chiameremo
AutoShutService) capace di monitorare
l'attività del PC su cui è in esecuzione (soprattutto virtual machine) e, in base
a certi criteri, di provocarne lo shutdown per evitare il consumarsi delle
risorse messe a disposizione (principalmente memoria RAM). Il tutto è stato davvero
un successo, perchè a quanto pare ho risolto una problematica che assillava da un
po' di tempo i sistemisti della società dove lavoro, che ricercavano
(inutilmente) su Internet un tool o un'utility che potesse aiutarli.
Al mio ritorno delle ferie estive, e quindi dopo circa 1 mese di attività sui server di produzione, è
stato riscontrato un piccolo problema che ho voluto sistemare questa mattina. In
pratica, l'installer che ho appositamente creato
imposta il servizio in modo tale che parta in automatico. Questa impostazione non ha causato problemi nel 95%
dei casi: in tutti i server (fisici e virtuali) che girano con Windows
2003, AutoShutService viene installato correttamente e parte ad ogni avvio
senza alcuna controindicazione. Su qualcuno dei server (fisici e virtuali) che
girano invece con Windows 2000 Server, invece, è
capitato che l'avvio in automatico fallisse
, mostrando sullo schermo un
messaggio di errore:
The service did not respond to the start or control request in a timely fashion.
Sebbene il contenuto sia chiaro, ho googlato molto alla ricerca di una possibile soluzione. La prima cosa
che ho pensato è che il mio servizio dipende da qualcun'altro,
come in effetti è. Concettualmente parlando, AutoShutService sfrutta due risorse
che posso essere critiche in questo scenario:
- EventLog: alla partenza, durante
l'esecuzione e al termine, AutoShutService logga in un event log dedicato
tutta una serie di informazioni più o meno interessanti.
- WMI: ad intervalli prestabiliti controllati da un System.Timers.Timer, utilizzo un PerformanceCounter per rilevare il "% Processor
Time".
In ambedue i casi, ho a che fare con due servizi di Windows: il primo è
Event Log, mentre il secondo è Windows Management
Instrumentation. Se riporto qui sotto uno snippet di codice tratto dal
costruttore dalla mia classe, salta subito all'occhio come effettivamente
l'event log venga utilizzato fin da subito, e quindi potrebbe essere la fonte
del mio problema:
public AutoShutService()
{
InitializeComponent();
settings = AutoShutSettings.Load();
if (settings.Log)
{
if (!EventLog.SourceExists("AutoShutService Source"))
{
EventLog.CreateEventSource("AutoShutService Source", "AutoShutdown Log");
}
serviceLog.Source = "AutoShutService Source";
serviceLog.Log = "AutoShutdown Log";
}
serviceLog.WriteEntry("AutoShutService starting...", EventLogEntryType.Information);
}
Se durante l'esecuzione del costruttore il servizio Event
Log non è ancora partito, anche il mio AutoShutService ha qualche
problema e fallisce il suo startup.
Risoluzione "da sistemisti"
Prima che mettessi mano al
mio progetto Visual Studio 2005, i sistemisti hanno provato a sistemare la cosa
autonomamente, ma con scarso successo! Dopo il setup del mio servizio, sono
andati nelle sue proprietà nell'elenco dei Services e, sotto il tab
Recovery, hanno provato a settare la ComboBox relativa alla
voci First failure, dicendo a Windows di far restartare il servizio
stesso. Questo cambiamento non ha avuto alcun effetto, quindi sono dovuto
rientrare in scena io.
AutoshutService: "Io dipendo da Event Log e da WMI, lo dici a
Windows, per favore?"
Più che il codice del servizio in sè, ho
dovuto toccare in realtà il suo installer, in modo tale da poter informare
Windows delle dipendenze di cui ho bisogno. La classe Installer di .NET espone una proprietà ServicesDependedOn, che
accoglie un'array di stringhe (string[]) che non è nient'altro che l'elenco dei
nomi dei servizi da cui dipendo. Quindi, in breve:
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
this.serviceInstaller1.ServicesDependedOn =
new string[] { "Eventlog", "WinMgmt" };
}
}
I nomi dei due servizi indicati sopra ("Eventlog" e "WinMgmt") non sono
quelli in chiaro che leggiamo
di solito, ma li ho reperiti nel registry all'interno della chiave
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. Questa semplice modifica
del mio installer ha fatto funzionare le cose: prima che AutoShutService venga
fatto partire, Windows si assicura che prima siano partiti l'event log e WMI. Da
tener presente che questa modifica è fattibile anche senza entrare nel codice:
quando si eredita dalla classe Installer VS2005 offre un designer, nel quale
vengono disposti un oggetto ServiceProcessInstaller ed un oggetto ServiceInstaller (entrambi contenuti nel namespace System.ServiceProcess
). Su quest'ultimo è possibile settare la proprietà
ServicesDependedOn
che prima abbiamo modificato in C#. Da qui possiamo dare anche una
descrizione del servizio e settare per esempio la proprietà StartType su uno dei valori
imposti dall'enum ServiceStartMode
.
Qualche miglioria alla gestione dell'Event
Log
Con .NET possiamo facilmente scrivere eventi nel nostro Event
Log. Nel costruttore riportato sopra, si vede chiaramente che posso utilizzare
la classe System.Diagnostics.EventLog
per scrivere nuove entry:
serviceLog.WriteEntry("AutoShutService starting...",
EventLogEntryType.Information);
La cosa interessante è che tramite l'istanza
serviceLog possiamo modificare il comportamento standard dell'event
log, per esempio settandone la dimensione massima (proprietà MaximumKilobytes), oppure il
numero minimo di giorni da mantere nell'event log (proprietà MinimumRetentionDays) oppure -
quello che serviva a me - decidere il comportamento dell'event log nel momento
in cui raggiunge la sua dimensione massima (metodo ModifyOverflowPolicy). Questo
metodo non fa altro che settare la proprietà (di sola-lettura) OverflowAction.