singrdk/base/Services/ServiceManager/ServiceManager.sg

2487 lines
120 KiB
Plaintext
Raw Normal View History

2008-03-05 09:52:00 -05:00
///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
2008-11-17 18:29:00 -05:00
//
2008-03-05 09:52:00 -05:00
//
2008-11-17 18:29:00 -05:00
//A service can be started for one of the following reasons:
// * The configuration says the service should always be running.
// * A client has connected to one of the advertised names of the service.
// * A management client has requested that the service be started.
2008-03-05 09:52:00 -05:00
//
//
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
using System;
2008-11-17 18:29:00 -05:00
using System.Diagnostics;
2008-03-05 09:52:00 -05:00
using System.Collections;
using System.Collections.Specialized;
using Microsoft.Contracts;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Applications;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Configuration;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Security;
using Microsoft.Singularity.ServiceManager;
using Microsoft.Singularity.Xml;
2008-11-17 18:29:00 -05:00
using Microsoft.Singularity.V1.Processes;
2008-03-05 09:52:00 -05:00
[assembly: ApplicationPublisherAttribute("singularity.microsoft.com")]
[assembly: AssertPrivilegeAttribute("$register-privilege.localhost")]
2008-11-17 18:29:00 -05:00
namespace Microsoft.Singularity.Services.ServiceManager
{
internal sealed class ServiceManager : ITracked
{
///
//<summary>
// The kernel creates the Service Manager process when the system starts.
// This happens in Kernel\Singularity.Io\IoSystem.sg, in the InitializeServiceManager()
// method. That method scans the system manifest (the in-memory, parsed XML document)
// for service definitions, and then builds the argument vector for this process.
// For each service, the method adds three arguments to the argument vector:
// the service name, the executable (manifest) name, and the service type, which is
// "managed" or "unmanaged". (We only support "managed".)
//
// The kernel also sets up the endpoint vector, with exactly one endpoint at entry zero.
// This is an instance of DirectoryServiceContract.Imp. The current implementation of
// the Service Manager does not use this endpoint, and so at startup time, we extract
// and delete that endpoint.
//
// Startup endpoints:
//
// 0 - DirectoryServiceContract.Imp:Ready
//
// 1 - ServiceProviderContract.Exp:Start
// This is a service provider channel registered at '/service/services'.
// The kernel creates this service provider channel before starting the
// Service Manager. Unit test apps can also create instances of the Service
// Manager at different paths.
//
//
//</summary>
//
public static int Main(string[]! args)
{
tracePrintLevel = TraceLevel.Notice;
Binder.Initialize();
Endpoint* in ExHeap ep0 = Process.GetStartupEndpoint(0);
if (ep0 == null) {
Dbg(TraceLevel.Error, "ERROR: Startup endpoint 0 is not set. Service Manager cannot start.");
Dbg(TraceLevel.Error, "Service Manager cannot start.");
return -1;
}
DirectoryServiceContract.Imp:Ready rootds = ep0 as DirectoryServiceContract.Imp:Ready;
if (rootds == null) {
delete ep0;
Dbg(TraceLevel.Error, "ERROR: Startup endpoint 0 is set, but is not DirectoryServiceContract.Imp:Ready.");
Dbg(TraceLevel.Error, "Service Manager cannot start.");
return -1;
}
Endpoint* in ExHeap ep1 = Process.GetStartupEndpoint(1);
if (ep1 == null) {
Dbg(TraceLevel.Error, "ERROR: Startup endpoint is not set. Service Manager cannot start.");
Dbg(TraceLevel.Error, "Service Manager cannot start.");
delete rootds;
return -1;
}
ServiceProviderContract.Exp:Start services_namespace_provider = ep1 as ServiceProviderContract.Exp:Start;
if (services_namespace_provider == null) {
Dbg(TraceLevel.Error, "ERROR: Startup endpoint is set, but is not ServiceProviderContract.Exp:Start.");
Dbg(TraceLevel.Error, "Service Manager cannot start.");
delete rootds;
delete ep1;
return -1;
}
ServiceManager! manager = new ServiceManager(rootds, services_namespace_provider);
try {
//
// All of the resources we need to run are now set up. Next, process the list of
// services passed to this process by the kernel. The kernel parses the system
// manifest, and builds the argument vector for this process by searching for
// <service> elements (xpath /system/serviceConfig/service). For each service,
// the kernel appends three strings to the argument vector: the service name,
// the executable (manifest) name, and service type ("managed" or "unmanaged").
//
manager.CreateInitialServices(args);
int result = manager.Run();
return result;
}
finally {
manager.Dispose();
}
}
ServiceManager(
[Claims]DirectoryServiceContract.Imp:Ready! rootds,
[Claims]ServiceProviderContract.Exp:Start! services_namespace_provider)
{
this.rootds = rootds;
this.services_namespace_provider = services_namespace_provider;
this.ForgetLastStartFailedInterval = TimeSpan.FromSeconds(30);
}
#region Data Fields
/// <summary>
/// Controls which calls to Dbg() are also sent to DebugStub.WriteLine().
/// </summary>
internal static TraceLevel tracePrintLevel;
readonly ServiceConnectRequest[]! connectRequestLookasideList = new ServiceConnectRequest[0x20];
int connectRequestLookasideCount;
TimeSpan ForgetLastStartFailedInterval;
readonly IDictionary/*<String,Service!>*/! services = new ListDictionary(CaseInsensitiveComparer.DefaultInvariant);
Service[] servicesSnapshot;
/// <summary>
/// This channel is provided by the kernel (or whatever process created this Service Manager process).
/// This endpoint is passed to this process as startup endpoint 0.
///
/// Currently, this field is not used. However, if this process does perform any Directory operations,
/// it should use this as its root.
/// </summary>
readonly DirectoryServiceContract.Imp! rootds;
/// <summary>
/// This channel is provided by the kernel (or whatever process created this Service Manager process).
/// This endpoint is a namespace service provider, registered with the Directory service, usually at
/// '/service/services'. However, a creating process may use a different name or path, so the Service Manager
/// should never assume that the path is '/service/services'.
///
/// This endpoint is passed to this process as startup endpoint 1.
/// </summary>
readonly ServiceProviderContract.Exp! services_namespace_provider;
/// <summary>
/// Contains clients channels who send service control requests to the Service Manager.
/// Clients in this set (state) have not selected a specific service.
/// </summary>
readonly ESet<ServiceManagerContract.Exp:Ready>! clients =
new ESet<ServiceManagerContract.Exp:Ready>();
/// <summary>
/// Contains clients channels who send service control requests to the Service Manager.
/// </summary>
readonly EMap<ServiceManagerContract.Exp:ServiceSelected,Service!>! svclients =
new EMap<ServiceManagerContract.Exp:ServiceSelected,Service!>();
/// <summary>
/// Contains clients that are in the "Enumerate" state; they are enumerating the set of services.
/// The IEnumerator reference enumerates Service.
/// </summary>
readonly EMap<ServiceManagerContract.Exp:EnumeratingServices,IEnumerator!>! clients_enumerating =
new EMap<ServiceManagerContract.Exp:EnumeratingServices,IEnumerator!>();
/// <summary>
/// The clients whose endpoints in this endpoint map have subscribed to the status of services,
/// and the client is the next sender. The client will send either WaitServiceChange, or
/// StopWatchingService.
/// </summary>
readonly EMap<ServiceManagerContract.Exp:WaitingServiceChange,ServiceWatcher!>! clients_readywatch =
new EMap<ServiceManagerContract.Exp:WaitingServiceChange,ServiceWatcher!>();
/// <summary>
/// Clients can connect to the Service Manager namespace (/service/services) using DirectoryServiceContract.
/// This can be used to enumerate service, and to connect to services.
/// </summary>
readonly EMap<DirectoryServiceContract.Exp:Ready,DirectoryClientInfo!>! dirs =
new EMap<DirectoryServiceContract.Exp:Ready,DirectoryClientInfo!>();
/// <summary>
/// Contains directory clients who are enumerating.
/// </summary>
readonly EMap<DirectoryServiceContract.Exp:EnumerateAck,DirectoryClientInfo!>! dirs_enumerating =
new EMap<DirectoryServiceContract.Exp:EnumerateAck,DirectoryClientInfo!>();
/// <summary>
/// The set of endpoints that want to be notified when the service starts.
/// All of these endpoints (stored in a TRef within ServiceStartWatcher) came from
/// clients who issued a StartServiceWait request.
/// </summary>
readonly ArrayList/*<ServiceStartWatcher>*/! serviceStartWatchers = new ArrayList();
/// <summary>
/// The set of endpoints that want to be notified when a specific service stops.
/// All of these endpoints (stored in a TRef within ServiceStopWatcher) came from
/// clients who issued a StopServiceWait request.
/// </summary>
readonly ArrayList/*<ServiceStopWatcher>*/! serviceStopWatchers = new ArrayList();
/// <summary>
/// Contains notifier endpoints of services that are being started.
/// </summary>
readonly EMap<ServiceStarterContract.Imp:Starting,ServiceStarter!>! service_starters =
new EMap<ServiceStarterContract.Imp:Starting,ServiceStarter!>();
/// <summary>
/// Contains the control endpoints of services that are starting. We're waiting for the "Success"
/// message from these services before we consider them started.
/// </summary>
readonly EMap<ServiceProcessContract.Imp:Starting,ServiceProcess!>! svcontrols_starting =
new EMap<ServiceProcessContract.Imp:Starting,ServiceProcess!>();
/// <summary>
/// Contains the control endpoints of services that we have sent a "StopService" request to.
/// </summary>
readonly EMap<ServiceProcessContract.Imp:Stopping,ServiceProcess!>! svcontrols_stopping =
new EMap<ServiceProcessContract.Imp:Stopping,ServiceProcess!>();
/// <summary>
/// Contains the control endpoints of services that we have sent a "Connect" request to.
/// </summary>
readonly EMap<ServiceProcessContract.Imp:Connecting,ServiceProcess!>! svcontrols_connecting =
new EMap<ServiceProcessContract.Imp:Connecting,ServiceProcess!>();
/// <summary>
/// Contains the notification channels that are connect to service processes, which are
/// in the "Ready" state. In this state, the service process can send messages to the
/// Service Manager. The Service Manager always responds immediately, so there are no
/// other EMaps or ESets for these channels.
/// </summary>
readonly EMap<ServiceEventContract.Exp:Ready,ServiceProcess!>! svevents =
new EMap<ServiceEventContract.Exp:Ready,ServiceProcess!>();
/// <summary>
/// Contains the control endpoints of services that we have sent a "Knock" message to.
/// </summary>
readonly EMap<ServiceProcessContract.Imp:Knocking,ServiceProcess!>! svcontrols_knocking =
new EMap<ServiceProcessContract.Imp:Knocking,ServiceProcess!>();
/// <summary>
/// Contains instance of ServiceManagerWatcher, representing those management clients that
/// have subscribed to the overall status of the Service Manager.
/// </summary>
readonly ArrayList/*<ServiceManagerWatcher>*/! managerWatchers = new ArrayList();
/// <summary>
/// Contains endpoints of management clients that have subscribed to the overall status of the
/// Service Manager, and are ready to wait for the next event, or to end their subscription.
/// The next possible messages in this state are WaitNextServiceManagerChange and
/// StopWatchingServiceManager.
/// </summary>
readonly EMap<ServiceManagerContract.Exp:ReadyWatchServiceManager,ServiceManagerWatcher!>! managerWatchersReady
= new EMap<ServiceManagerContract.Exp:ReadyWatchServiceManager,ServiceManagerWatcher!>();
#endregion
public void Dispose()
{
delete clients;
delete clients_enumerating;
delete clients_readywatch;
delete service_starters;
delete svclients;
delete svcontrols_starting;
delete svcontrols_stopping;
delete svcontrols_connecting;
delete dirs;
delete dirs_enumerating;
delete rootds;
delete svevents;
delete svcontrols_knocking;
delete managerWatchersReady;
delete services_namespace_provider;
}
/// <summary>
/// Main service loop.
/// </summary>
int Run()
{
expose(this) {
//
// Now we enter our main message loop.
//
Dbg(TraceLevel.Notice, "Service Manager has started successfully.");
for (;;) {
//
// Before we process any new messages, check the integrity and
// consistency of our data structures.
//
AssertIntegrity();
ScanForStoppedProcesses();
switch receive {
#region Event handlers for message from Directory, for the '/service' registration
case services_namespace_provider.Connect(ServiceContract.Exp:Start! exp):
{
ServiceManagerContract.Exp:Start client = exp as ServiceManagerContract.Exp:Start;
DirectoryServiceContract.Exp:Start dir = exp as DirectoryServiceContract.Exp:Start;
if (client != null) {
Dbg(TraceLevel.Audit, "A client has connected, using ServiceManagerContract.");
client.SendSuccess();
clients.Add(client);
services_namespace_provider.SendAckConnect();
}
else if (dir != null) {
Dbg(TraceLevel.Audit, "A client has connected, using DirectoryServiceContract.");
DirectoryClientInfo info = new DirectoryClientInfo("/");
dir.SendSuccess();
dirs.Add(dir, info);
services_namespace_provider.SendAckConnect();
}
else {
Dbg(TraceLevel.Audit, "A client has connected, but has offered an unsupported channel contract.");
services_namespace_provider.SendNackConnect(exp);
break;
}
break;
}
case services_namespace_provider.ChannelClosed():
Dbg(TraceLevel.Audit, "The Directory has closed the namespace provider channel. Service manager will now terminate.");
return -1;
#endregion
#region Event handlers for 'clients' endpoint set
// These are event handlers for ServiceManagerContract clients in the "Ready" state.
// These are management clients that have connected to the Service Manager, but have
// not selected any particular service to operate on.
case client.SelectService(char[]! in ExHeap serviceName) in clients:
{
string! name = Bitter.ToString2(serviceName);
delete serviceName;
Service service = FindService(name);
if (service != null) {
Dbg(TraceLevel.Audit, "Client has selected service '{0}'.", name);
client.SendOk();
svclients.Add(client, service);
// -XXX- add reference count on service?
}
else {
Dbg(TraceLevel.Audit, "Client wants to select service '{0}', but there is no such service.", name);
client.SendRequestFailed(ServiceError.ServiceNotFound);
clients.Add(client);
}
break;
}
case client.CreateService(ServiceConfig config) in clients:
{
InternalServiceConfig local_config = new InternalServiceConfig(ref config);
ServiceError error = CreateService(local_config);
if (error == ServiceError.None) {
client.SendOk();
}
else {
Dbg(TraceLevel.Audit, "The request to create a service has failed, error = " + ServiceEnums.ToString(error));
client.SendRequestFailed(error);
}
config.Dispose();
clients.Add(client);
break;
}
case client.EnumerateServices(ServiceInfo[]! in ExHeap infos) in clients:
{
// It's possible to create just the enumerator here, and allow
// further messages (client.MoveNext) to drive that enumerator.
// However, it's also possible for the collection being enumerated
// to change, if a service is added or deleted. That would cause
// an exception during use of the enumerator later. Instead, we
// make a copy of the services.Values collection, if necessary.
Service[]! snapshot = GetServicesSnapshot();
IEnumerator! enumerator = snapshot.GetEnumerator();
EnumerateServiceInfos(client, infos, enumerator);
break;
}
case client.WatchServiceManager(ServiceManagerEventMask mask) in clients:
// The client wants to subscribe to the overall status and configuration
// of the Service Manager.
if (mask == 0) {
Dbg(TraceLevel.Audit, "Received 'WatchServiceManager', but the event mask specified is zero.");
client.SendRequestFailed(ServiceError.InvalidArguments);
clients.Add(client);
break;
}
const ServiceManagerEventMask known_events = ServiceManagerEventMask.AnyServiceConfig
| ServiceManagerEventMask.AnyServiceStatus;
if ((mask & ~known_events) != 0) {
Dbg(TraceLevel.Audit, "Received 'WatchServiceManager', but the client has specified events (bits) that we don't recognized.");
client.SendRequestFailed(ServiceError.InvalidArguments);
clients.Add(client);
break;
}
ServiceManagerWatcher watcher = new ServiceManagerWatcher();
watcher.DesiredEvents = mask;
watcher.EventsMissed = mask;
client.SendOk();
managerWatchers.Add(watcher);
managerWatchersReady.Add(client, watcher);
Dbg(TraceLevel.Audit, "A client has subscribed to global Service Manager events.");
Dbg(TraceLevel.Audit, "Watching: all service config = {0}, all service status = {1}",
((mask & ServiceManagerEventMask.AnyServiceConfig) != 0).ToString(),
((mask & ServiceManagerEventMask.AnyServiceStatus) != 0).ToString());
break;
case client.ChannelClosed() in clients:
{
// Dbg(TraceLevel.Audit, "A client client has disconnected.");
delete client;
break;
}
#endregion
#region Handlers for management clients in the ReadyWatchServiceManager state
// These event handlers are for management clients that have subscribed to the
// overall state of the Service Manager.
case client.WaitNextServiceManagerChange() in managerWatchersReady ~> watcher:
if (watcher.EventsMissed != 0) {
Dbg(TraceLevel.Audit, "Received 'WaitNextServiceManagerChange' on SM watcher, responding immediately with mask {0:x8}", ((uint)watcher.EventsMissed));
client.SendServiceManagerChanged(watcher.EventsMissed);
managerWatchersReady.Add(client, watcher);
}
else {
Dbg(TraceLevel.Audit, "Received 'WaitNextServiceManagerChange' on SM watcher; no events ready, parking endpoint.");
watcher.SetEndpoint(client);
}
break;
case client.StopWatchingServiceManager() in managerWatchersReady ~> watcher:
// The client wants to end its subscription to the Service Manager events.
managerWatchers.Remove(watcher);
watcher.Dispose();
clients.Add(client);
break;
#endregion
#region Handlers for 'svclients': ServiceManagerContract clients bound to a service
// These event handlers are for management clients that have connected, and
// have successfully selected a specific service.
case svclient.UnselectService() in svclients ~> service:
Dbg(TraceLevel.Audit, service, "Client has unselected service.");
clients.Add(svclient);
// -XXX- decrement reference count on service?
break;
case svclient.StartServiceNoWait() in svclients ~> service:
{
Dbg(TraceLevel.Audit, "Received StartServiceNoWait request for service '{0}.", service.ServiceName);
ServiceError error = StartServiceProcess(service);
if (error == ServiceError.None) {
Dbg(TraceLevel.Audit, service, " Request succeeded.");
svclient.SendServiceStarting();
}
else {
Dbg(TraceLevel.Audit, service, " Request failed: error = " + error);
svclient.SendRequestFailed(error);
}
svclients.Add(svclient, service);
PushService(service);
break;
}
case svclient.StartServiceWait() in svclients ~> service:
{
Dbg(TraceLevel.Audit, service, "Received StartServiceWait request for service '{0}'.", service.ServiceName);
ServiceError error = StartServiceProcess(service);
if (error == ServiceError.None) {
Dbg(TraceLevel.Audit, service, " Request succeeded. Parking endpoint in serviceStartWatchers.");
ServiceStartWatcher watcher = new ServiceStartWatcher(service, svclient);
serviceStartWatchers.Add(watcher);
}
else {
Dbg(TraceLevel.Audit, service, " Request failed: error = " + error);
svclient.SendRequestFailed(error);
svclients.Add(svclient, service);
}
PushService(service);
break;
}
case svclient.StopServiceNoWait() in svclients ~> service:
Dbg(TraceLevel.Audit, service, "Received request to stop service. Client does not want to be notified when service stops.");
ServiceError error = StopService(service);
if (error == ServiceError.None) {
svclient.SendServiceStopping();
}
else {
svclient.SendRequestFailed(error);
}
svclients.Add(svclient, service);
PushService(service);
break;
case svclient.StopServiceWait() in svclients ~> service:
Dbg(TraceLevel.Audit, service, "Received request to stop service. NYI");
svclient.SendRequestFailed(ServiceError.Unknown);
svclients.Add(svclient, service);
break;
case svclient.WatchServiceStatus() in svclients ~> service:
{
// A client wants to watch the status of a service.
// For now, we only allow watching services that exist.
// At some future point, we should allow subscriptions
// to services that do not exist. We should notifiy
// subscribers when the service is created, deleted, etc.
ServiceWatcher watcher = new ServiceWatcher(service);
service.StatusWatchers.Add(watcher);
svclient.SendOk();
clients_readywatch.Add(svclient, watcher);
Dbg(TraceLevel.Audit, service, "A client is now watching the status of a service.");
// -XXX- add reference to service?
break;
}
case svclient.DeleteService() in svclients ~> service:
ProcessDeleteServiceRequest(svclient, service);
svclients.Add(svclient, service);
break;
case svclient.QueryServiceConfig() in svclients ~> service:
{
ServiceConfig config = new ServiceConfig();
service.GetConfig(ref config);
svclient.SendCurrentServiceConfig(config);
svclients.Add(svclient, service);
break;
}
case svclient.QueryServiceStatus() in svclients ~> service:
{
InternalServiceStatus status = service.LastServiceStatusPublished;
ServiceStatus exstatus = status.ToExchange();
svclient.SendCurrentServiceStatus(exstatus);
svclients.Add(svclient, service);
break;
}
case svclient.ChannelClosed() in svclients ~> service:
// Dbg(TraceLevel.Audit, service, "A client that had selected this service has disconnected.");
delete svclient;
break;
case svclient.EnableService(bool enable) in svclients ~> service:
if (enable)
Dbg(TraceLevel.Audit, service, "A client wants to ENABLE the service.");
else
Dbg(TraceLevel.Audit, service, "A client wants to DISABLE the service.");
service.Config.IsAdministrativelyDisabled = !enable;
PushService(service);
svclient.SendOk();
svclients.Add(svclient, service);
break;
case svclient.TerminateServiceAllProcesses() in svclients ~> service:
{
Dbg(TraceLevel.Audit, service, "Received 'TerminateServiceAllProcesses'. Terminating all processes for this service.");
foreach (ServiceProcess! process in service.Processes) {
TerminateServiceProcess(process);
}
PushService(service);
svclient.SendOk();
svclients.Add(svclient, service);
break;
}
case svclient.TerminateServiceProcess(int processId) in svclients ~> service:
{
Dbg(TraceLevel.Audit, service, "Received 'TerminateServiceProcess' for process id {0}.", processId);
foreach (ServiceProcess! process in service.Processes) {
Process systemProcess = process.Process;
if (systemProcess != null && systemProcess.Id == processId) {
TerminateServiceProcess(process);
break;
}
}
PushService(service);
svclient.SendOk();
svclients.Add(svclient, service);
break;
}
#endregion
#region Handlers for 'clients_readywatch' endpoint map
// These event handlers are for clients that have subscribed to the
// status of a particular service.
case client.WaitServiceChange() in clients_readywatch ~> watcher:
{
// A client wants to receive the next status change.
Assert(!watcher.EndpointCanSend);
if (watcher.MissedChange) {
// The watcher missed the last update, because the watcher had not yet
// placed its channel in a state where the service manager could send
// a status update. So now we send a status update immediately.
Dbg(TraceLevel.Audit, watcher.Service, "Received WaitServiceChange - client missed an update; sending status now");
watcher.MissedChange = false;
InternalServiceStatus status = watcher.Service.LastServiceStatusPublished;
ServiceStatus exstatus = status.ToExchange();
client.SendServiceStatusChanged(exstatus, true);
clients_readywatch.Add(client, watcher);
}
else {
// The watcher wants an update. We move the endpoint from the local
// stack frame to the TRef in watcher, and mark the watcher as ready.
Dbg(TraceLevel.Audit, watcher.Service, "Received WaitServiceChange - watcher is now armed");
watcher.TakeEndpoint(client);
}
break;
}
case client.ChannelClosed() in clients_readywatch ~> watcher:
// Dbg(watcher.Service, "A client that was watching service status has disconnected.");
watcher.Service.StatusWatchers.Remove(watcher);
delete client;
break;
#endregion
#region Handlers for endpoints in 'clients_enumerating'
// These are the event handlers for ServiceManagerContract clients that
// are in the process of enumerating all services.
case client.EnumerateServices(ServiceInfo[]! in ExHeap infos) in clients_enumerating ~> enumerator:
EnumerateServiceInfos(client, infos, enumerator);
break;
case client.EndEnumeration() in clients_enumerating ~> enumerator:
Dbg(TraceLevel.Audit, "A client client has finished enumerating services.");
// There is no response message.
// enumerator.Dispose();
clients.Add(client);
break;
case client.ChannelClosed() in clients_enumerating ~> enumerator:
// Dbg(TraceLevel.Audit, "A client client has disconnected.");
// enumerator.Dispose();
delete client;
break;
#endregion
#region Wiring that allows us to know when ServiceStarter has completed.
case notifier.Succeeded() in service_starters ~> starter:
{
ServiceProcess! process = starter.Process;
Service! service = starter.Service;
Dbg(TraceLevel.Audit, process, "Worker thread has started service process. Now waiting for 'Success' message.");
assert process.State == ServiceProcessState.Starting;
assert process.Process == null;
delete notifier;
ServiceProcessContract.Imp:Starting! svcontrol;
ServiceEventContract.Exp:Ready! svevent;
Process! system_process;
starter.GetSuccessfulResults(out svcontrol, out svevent, out system_process);
process.SetProcess(system_process);
process.SetEventEndpoint(svevent);
svcontrols_starting.Add(svcontrol, process);
PushService(service);
break;
}
case notifier.Failed(error) in service_starters ~> starter:
{
// The notifier has already disposed of the process.
Service! service = starter.Service;
ServiceProcess! svprocess = starter.Process;
service.Processes.Remove(svprocess);
Dbg(TraceLevel.Audit, service, "Failed to start a service process. Error: {0}", error);
delete notifier;
service.LastStartError = error;
service.LastStartFailed = true;
service.LastStartFailedTime = SchedulerTime.Now;
PushService(service);
break;
}
#endregion
#region Handlers for 'svcontrols_starting'
// These handlers process messages that we receive from a "control" channel
// that we created for communicating with a service process.
//
// If process.IsTerminated is true when we receive a message on a control
// channel, then we have abandoned this process (or even terminated it), but
// because we cannot remove endpoints from endpoint maps, we can still receive
// a message from it. This could happen due to a race condition, or if the
// service transferred its control endpoint to a different process. In all
// cases, we must guard against receiving this message late.
case svcontrol.StartSucceeded() in svcontrols_starting ~> process:
{
// This message indicates that a service process has successfully started.
// We verify that we are still interested starting the service, and then
// we mark the service "Running", and then we check to see if there are
// any clients waiting to connect to the service.
Service! service = process.Service;
do {
if (process.State != ServiceProcessState.Starting) {
Dbg(TraceLevel.Audit, process, "Received 'StartSucceeded' from a process, but the process was abandoned earlier.");
Dbg(TraceLevel.Audit, process, "The process will be ignored, and the orphaned control channel will be closed.");
delete svcontrol;
break;
}
if (service.IsAdministrativelyDisabled) {
Dbg(TraceLevel.Audit, process, "The service was disabled after the service process was started.");
process.State = ServiceProcessState.Stopping;
process.NeedSendStopControl = true;
svcontrol.SendStop();
// -XXX- Need to set control endpoint state to Stopping
svcontrols_stopping.Add(svcontrol, process);
break;
}
Dbg(TraceLevel.Audit, process, "A service process has indicated that it has successfully started.");
if (process.State == ServiceProcessState.Starting) {
process.State = ServiceProcessState.Running;
if (process.EventEndpointIsReady) {
// Dbg(TraceLevel.Audit, process, "Binding service event channel.");
ServiceEventContract.Exp:Ready! svevent = process.GetEventEndpoint(EventChannelState.WaitingEvent);
svevents.Add(svevent, process);
}
else {
Dbg(TraceLevel.Audit, process, "Warning: No event channel was available for this service process.");
}
process.State = ServiceProcessState.Running;
process.SetControlEndpoint(svcontrol);
Dbg(TraceLevel.Audit, process, "The service process is now running.");
}
else if (process.State == ServiceProcessState.Running) {
Dbg(TraceLevel.Audit, process, "Received 'StartSucceeded' from a process, but it is already in the running state!");
Dbg(TraceLevel.Audit, process, "This should never happen.");
Util.IllegalStateReached();
process.SetControlEndpoint(svcontrol);
}
else {
Dbg(TraceLevel.Audit, process, "Illegal value for ServiceProcessState!");
Util.IllegalStateReached();
delete svcontrol;
}
} while (false);
PushService(service);
break;
}
case svcontrol.StartFailed(ServiceError error) in svcontrols_starting ~> process:
{
// This message indicates that a service process tried to start (initialize),
// but something went wrong, and so the service process cannot be used.
// In this case, we mark the service process as "defective", and close all
// channels to it. It's not yet clear what we should do with the process --
// wait for it to exit? terminate it immediately?
Service! service = process.Service;
switch (process.State) {
case ServiceProcessState.Starting:
Dbg(TraceLevel.Audit, process, "Received 'StartFailed'; service indicates that it has failed to start.");
process.State = ServiceProcessState.Stopping;
service.LastStartFailed = true;
service.LastStartFailedTime = SchedulerTime.Now;
MarkProcessDefective(process);
FailAllConnectRequests(service, ErrorCode.Unknown);
break;
case ServiceProcessState.Running:
// This should never happen.
Dbg(TraceLevel.Audit, process, "Received 'StartFailed' from a service, but we believe the service is already running.");
Util.IllegalStateReached();
break;
case ServiceProcessState.Stopping:
// We started a service process, and then changed our minds.
// This is a rare, but normal (acceptable) situation.
Dbg(TraceLevel.Audit, process, "Received 'StartFailed' from a service. We had already abandoned this service process, so no big deal.");
break;
default:
Util.IllegalStateReached();
break;
}
delete svcontrol;
process.CloseControlEndpoint();
PushService(process.Service);
break;
}
#endregion
#region Handlers for 'svcontrols_knocking'
case svcontrol.Alive() in svcontrols_knocking ~> process:
Dbg(TraceLevel.Audit, process, "Received 'Alive' from service.");
process.SetControlEndpoint(svcontrol);
PushService(process.Service);
break;
case svcontrol.ChannelClosed() in svcontrols_knocking ~> process:
Dbg(TraceLevel.Audit, process, "A service has closed its control channel unexpectedly.");
delete svcontrol;
MarkProcessDefective(process);
process.CloseControlEndpoint();
PushService(process.Service);
break;
#endregion
#region Event handlers for 'svcontrols_stopping'
case svcontrol.AckStop() in svcontrols_stopping ~> process:
{
// A service process has acknowledged the Service Manager's request
// to stop the process. We leave the process in the "stopping" state;
// later, when the process terminates, we'll remove the process from
// the process list for this service.
Dbg(TraceLevel.Audit, process, "Received 'AckStop' from service process.");
Assert(process.State == ServiceProcessState.Stopping);
delete svcontrol;
PushService(process.Service);
break;
}
case svcontrol.Busy() in svcontrols_stopping ~> process:
Dbg(TraceLevel.Audit, process, "Received 'Busy' from a service process that we are trying to stop.");
Dbg(TraceLevel.Audit, process, "Not sure what to do with this process!");
process.SetControlEndpoint(svcontrol);
PushService(process.Service);
break;
#endregion
#region Event handlers for 'svcontrols_connecting'
case svcontrol.AckConnect() in svcontrols_connecting ~> process:
{
if (process.State == ServiceProcessState.Running) {
Dbg(TraceLevel.Audit, process, "Received 'AckConnect' from service process.");
if (process.HasCurrentConnectDir) {
SchedulerTime timeStarted;
SchedulerTime timeRoutedToService;
DirectoryServiceContract.Exp:Ready! dir;
string! subpath;
DirectoryClientInfo! dirinfo;
process.GetCurrentConnectDir(out dir, out dirinfo, out subpath, out timeStarted, out timeRoutedToService);
dir.SendAckBind();
SchedulerTime timeFinished = SchedulerTime.Now;
dirs.Add(dir, dirinfo);
TimeSpan elapsed = timeFinished - timeStarted;
Dbg(TraceLevel.Audit, process, "Routing AckBind response to dir client. Service time: {0}", elapsed.ToString());
}
else {
Dbg(TraceLevel.Audit, process, "No current connect dir is set!! Cannot route bind response!");
DebugStub.Break();
}
}
else {
Dbg(TraceLevel.Audit, process, "Received 'AckConnect' from service process, but the process is stopping. Message will be ignored.");
}
process.SetControlEndpoint(svcontrol);
PushService(process.Service);
break;
}
case svcontrol.NakConnect(ErrorCode error, ServiceContract.Exp:Start exp) in svcontrols_connecting ~> process:
{
Dbg(TraceLevel.Audit, process, "Received 'NakConnect' from service, error = ", SdsUtils.ErrorCodeToString(error));
delete exp;
if (process.HasCurrentConnectDir) {
SchedulerTime timeStarted;
SchedulerTime timeRoutedToService;
DirectoryServiceContract.Exp:Ready! dir;
string! subpath;
DirectoryClientInfo! dirinfo;
process.GetCurrentConnectDir(out dir, out dirinfo, out subpath, out timeStarted, out timeRoutedToService);
dir.SendNakBind(null, error);
SchedulerTime now = SchedulerTime.Now;
TimeSpan elapsed = now - timeStarted;
Dbg(TraceLevel.Audit, process, "Routing NakBind response to client. Time elapsed: " + elapsed.ToString());
dirs.Add(dir, dirinfo);
}
else {
Dbg(TraceLevel.Audit, process, "No current connect dir is set!! Cannot route bind response!");
DebugStub.Break();
}
process.SetControlEndpoint(svcontrol);
PushService(process.Service);
break;
}
case svcontrol.ChannelClosed() in svcontrols_connecting ~> process:
Dbg(TraceLevel.Audit, process, "A service has closed its control channel unexpectedly.");
delete svcontrol;
MarkProcessDefective(process);
PushService(process.Service);
break;
#endregion
#region Handlers for events received from services
// These event handlers receive messages on an "event" channel that we have
// set up between the Service Manager and a service process. All of these
// handlers verify that we still want to receive these messages, by checking
// that process.State == ServiceProcessState.Running. We do this because
// we cannot currently remove endpoints from EMaps or ESets. Once the
// Service Manager has decided to tear down a service process, it sets the
// process state to Stopping, which is the cue for these event handlers to
// avoid processing received messages, and then to close the event channel.
case svevent.HealthChanged(ServiceHealth health) in svevents ~> process:
{
if (process.State == ServiceProcessState.Running) {
Dbg(TraceLevel.Audit, process, "Received 'HealthChanged' from service process, health = {0}", ServiceEnums.ToString(health));
process.Health = health;
svevent.SendAck();
svevents.Add(svevent, process);
}
else {
Dbg(TraceLevel.Audit, process, "Received 'HealthChanged' message, but on an orphaned event channel. Closing channel.");
process.CloseEventEndpoint();
svevent.SendAck();
delete svevent;
}
PushService(process.Service);
break;
}
case svevent.LoadChanged(ServiceLoad load) in svevents ~> process:
{
if (process.State == ServiceProcessState.Running) {
Dbg(TraceLevel.Audit, process, "Received 'LoadChanged' from service process, load = {0}", ServiceEnums.ToString(load));
process.Load = load;
svevent.SendAck();
svevents.Add(svevent, process);
}
else {
Dbg(TraceLevel.Audit, process, "Received 'LoadChanged' message, but on an orphaned event channel. Closing channel.");
process.CloseEventEndpoint();
svevent.SendAck();
delete svevent;
}
PushService(process.Service);
break;
}
case svevent.ChannelClosed() in svevents ~> process:
{
if (process.State == ServiceProcessState.Running) {
Dbg(TraceLevel.Audit, process, "A service process has unexpectedly closed its event channel!");
Dbg(TraceLevel.Audit, process, "The Service Manager will no longer be able to receive event messages from the service process.");
process.CloseEventEndpoint();
}
else {
Dbg(TraceLevel.Audit, process, "A service process has closed its event channel, as expected.");
process.CloseEventEndpoint();
}
delete svevent;
break;
}
#endregion
#region Handlers for DirectoryServiceContract
// The Service Manager does not support most of the requests defined by
// DirectoryServiceContract. The only requests supported are Bind
// and the enumeration pattern.
case dir.Bind(char[]! in ExHeap expath, ServiceContract.Exp:Start! exp) in dirs ~> dirinfo:
{
SchedulerTime timeStarted = SchedulerTime.Now;
string! path = Bitter.ToString2(expath);
delete expath;
Dbg(TraceLevel.Audit, dirinfo, "Received Bind('{0}').", path);
string! serviceName;
string! subpath;
int start = 0;
if (path.Length > 0 && path[0] == '/')
start++;
int index = path.IndexOf('/', start);
if (index != -1) {
serviceName = path.Substring(start, index);
subpath = path.Substring(index + 1);
}
else {
serviceName = path.Substring(start);
subpath = "";
}
Service service = FindService(serviceName);
if (service == null) {
Dbg(TraceLevel.Audit, dirinfo, "There is no service named '{0}'. Rejecting bind.", serviceName);
dir.SendNakBind(exp, ErrorCode.NotFound);
dirs.Add(dir, dirinfo);
break;
}
if (service.IsAdministrativelyDisabled) {
Dbg(TraceLevel.Audit, dirinfo, "The service '{0}' is administratively disabled. Rejecting bind.", service.ServiceName);
dir.SendNakBind(exp, ErrorCode.AccessDenied);
dirs.Add(dir, dirinfo);
break;
}
if (service.IsDefective) {
Dbg(TraceLevel.Audit, dirinfo, "The service '{0}' is currently considered defective. Rejecting bind.", service.ServiceName);
dir.SendNakBind(exp, ErrorCode.AccessDenied);
dirs.Add(dir, dirinfo);
break;
}
Dbg(TraceLevel.Audit, dirinfo, "Enqueuing connect request for service '{0}' at subpath '{1}'.", service.ServiceName, subpath);
service.EnqueueConnectRequest(dir, dirinfo, subpath, exp, timeStarted);
PushService(service);
break;
}
case dir.BeginEnumeration() in dirs ~> dirinfo:
{
// The only "path" that we support is the root directory.
if (dirinfo.Path != "/") {
Dbg(TraceLevel.Audit, "A directory client wants to enumerate, but at a bogus path '{0}'.", dirinfo.Path);
dir.SendEnumerationTerminated(ErrorCode.NotFound);
dirs.Add(dir, dirinfo);
break;
}
Service[]! snapshot = GetServicesSnapshot();
EnumerationRecords[]! in ExHeap results = new[ExHeap] EnumerationRecords[snapshot.Length];
for (int i = 0; i < snapshot.Length; i++) {
Service! service = (!)snapshot[i];
expose(results[i]) {
delete results[i].Path;
results[i].Path = Bitter.FromString2(service.ServiceName);
results[i].Type = NodeType.ServiceProvider;
}
}
dir.SendEnumerationEntries(results, false);
dirs_enumerating.Add(dir, dirinfo);
break;
}
case dir.ReadEnumeration() in dirs_enumerating ~> dirinfo:
// We already sent everything in the first request.
// So this is a no-op.
dir.SendEnumerationTerminated(ErrorCode.NoError);
dirs.Add(dir, dirinfo);
break;
case dir.EndEnumeration() in dirs_enumerating ~> dirinfo:
dirs.Add(dir, dirinfo);
break;
case dir.ChannelClosed() in dirs_enumerating ~> dirinfo:
delete dir;
break;
case dir.Notify(path, pattern, existing, imp) in dirs ~> dirinfo:
delete path;
delete pattern;
dir.SendNakNotify(imp, ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.GetAttributes(path) in dirs ~> dirinfo:
delete path;
dir.SendNakGetAttributes(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.QueryACL(path, effective) in dirs ~> dirinfo:
delete path;
dir.SendNakQueryACL(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.Register(path, imp) in dirs ~> dirinfo:
delete path;
dir.SendNakRegister(imp, ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.Deregister(path) in dirs ~> dirinfo:
delete path;
dir.SendNakDeregister(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.CreateDirectory(path) in dirs ~> dirinfo:
delete path;
dir.SendNakCreateDirectory(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.DeleteDirectory(path) in dirs ~> dirinfo:
delete path;
dir.SendNakDeleteDirectory(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.CreateFile(path) in dirs ~> dirinfo:
delete path;
dir.SendNakCreateFile(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.CreateAndBindFile(path, exp) in dirs ~> dirinfo:
delete path;
dir.SendNakCreateAndBindFile(exp, ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.DeleteFile(path) in dirs ~> dirinfo:
delete path;
dir.SendNakDeleteFile(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.StoreACL(path, pattern, descendantPattern) in dirs ~> dirinfo:
delete path;
delete pattern;
delete descendantPattern;
dir.SendNakStoreACL(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.CreateLink(path, value) in dirs ~> dirinfo:
delete path;
delete value;
dir.SendNakCreateLink(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.DeleteLink(path) in dirs ~> dirinfo:
delete path;
dir.SendNakDeleteLink(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
case dir.GetLinkValue(path) in dirs ~> dirinfo:
delete path;
dir.SendNakGetLinkValue(ErrorCode.NotSupported);
dirs.Add(dir, dirinfo);
break;
#endregion
case timeout(TimeSpan.FromSeconds(30)):
// For now, we pop out of our select now and then, to check for
// changes in the status of child processes, etc. Need a better
// way to do this. Don't print anything here; too noisy.
PushAllServices();
break;
}
}
}
}
/// <summary>
/// This method marks a process as "defective", which prevents new clients from being routed
/// to the process. The Service Manager closes all of its endpoints (if possible; some endpoints
/// may be in endpoint maps, and so they will not be closed until a message is later received).
/// </summary>
void MarkProcessDefective(ServiceProcess! process)
{
Dbg(TraceLevel.Audit, process, "Marking process as defective.");
process.IsDefective = true;
process.CloseControlEndpoint();
process.CloseEventEndpoint();
// If there was a DirectoryServiceContract client who had requested a bind,
// and that client had been routed to this service process, then extract the
// DirectoryServiceContract endpoint and complete the bind request with an error.
if (process.HasCurrentConnectDir) {
DirectoryServiceContract.Exp:Ready! dir;
DirectoryClientInfo! dirinfo;
process.GetCurrentConnectDir(out dir, out dirinfo);
dir.SendNakBind(null, ErrorCode.Unknown);
expose (this) { dirs.Add(dir, dirinfo); }
}
}
/// <summary>
/// This method is called whenever a global Service Manager event has occurred, which includes:
///
/// * The status of any service has changed.
/// * The configuration of any service has changed.
/// * A service has been created.
/// * A service has been deleted.
///
/// These events are mapped to bits within ServiceManagerEventMask. All of the "status"
/// events map to ServiceManagerEventMask.AnyServiceStatus, and all of the "config" events
/// map to ServiceManagerEventMask.AnyServiceConfig.
///
/// No attempt is made to notify watchers of the specifics of these events. Doing so requires
/// keeping track of much more state. If a management client wants to know about details, it
/// should query the Service Manager after being notified of a status or config change.
/// </summary>
void NotifyServiceManagerWatchers(ServiceManagerEventMask events)
{
if (events == 0)
return;
expose(this) {
foreach (ServiceManagerWatcher! watcher in managerWatchers) {
// If a watcher endpoint is available, then we send a notification message
// immediately. If the watcher endpoint is not available, which typically
// will occur when SM events occur more quickly than a watcher can acknowledge
// them, we set bits in the watcher's EventsMissed mask. Later, when the
// watcher acknowledges a previous notification, it will be immediately
// notified with the events in the EventsMissed mask.
// Only consider those events that this watcher has subscribed to.
ServiceManagerEventMask events_relevant = (events & watcher.DesiredEvents);
if (events_relevant == 0)
continue;
if (watcher.IsEndpointReady) {
Dbg(TraceLevel.Audit, "Sending ServiceManagerChanged to global watcher");
ServiceManagerContract.Exp:WaitingServiceManagerChange! ep = watcher.GetEndpoint();
ep.SendServiceManagerChanged(events_relevant);
managerWatchersReady.Add(ep, watcher);
}
else {
Dbg(TraceLevel.Audit, "A global watcher is not ready to receive events; marking for later delivery.");
watcher.EventsMissed = watcher.EventsMissed | events_relevant;
}
}
}
}
void ProcessDeleteServiceRequest(
ServiceManagerContract.Exp! svclient,
Service! service)
{
Dbg(TraceLevel.Audit, service, "Received request to delete service.");
if (service.IsMarkedForDeletion) {
Dbg(TraceLevel.Audit, service, "The service is already marked for deletion; rejecting request.");
svclient.SendRequestFailed(ServiceError.ServiceIsMarkedForDeletion);
return;;
}
if (service.IsDeleted) {
Dbg(TraceLevel.Audit, service, "The service is already deleted; rejecting request.");
svclient.SendRequestFailed(ServiceError.ServiceIsDeleted);
return;
}
service.IsMarkedForDeletion = true;
svclient.SendOk();
// We may not be able to delete the service yet.
bool can_delete_now = true;
if (service.Processes.Count != 0) {
Dbg(TraceLevel.Audit, service, "Cannot delete service yet; still have service processes.");
can_delete_now = false;
StopService(service);
}
if (can_delete_now) {
service.IsDeleted = true;
services.Remove(service.ServiceName);
Dbg(TraceLevel.Audit, service, "The service has been deleted.");
}
else {
Dbg(TraceLevel.Audit, service, "The service has been marked for deletion.");
}
PushService(service);
}
void DeleteServiceProcessResources(ServiceProcess! process)
{
expose(this) {
Dbg(TraceLevel.Audit, process, "DeleteServiceProcessResources - deleting service process resources");
if (process.HasCurrentConnectDir) {
Dbg(TraceLevel.Audit, process, " A connect request was active. Rejecting bind.");
DirectoryServiceContract.Exp:Ready! dir;
DirectoryClientInfo! dirinfo;
string! subpath;
process.GetCurrentConnectDir(out dir, out dirinfo);
dir.SendNakBind(null, ErrorCode.Unknown);
dirs.Add(dir, dirinfo);
}
if (process.ControlEndpointIsReady) {
Dbg(TraceLevel.Audit, process, "Closing control channel");
ServiceProcessContract.Imp! svcontrol = process.GetControlEndpoint(ControlEndpointState.Disconnected);
delete svcontrol;
}
}
}
/// <summary>
/// For a particular service, this method checks to see if there is any reason to send
/// a message to the control channel of a service. Control messages
/// </summary>
void PushControlMessages(ServiceProcess! process)
{
expose (this) {
Service! service = process.Service;
if (!process.ControlEndpointIsReady) {
// If the control endpoint is not ready, there's nothing we can do here.
Dbg(TraceLevel.Audit, process, "PushControlMessages - control endpoint is not ready");
return;
}
switch (process.State) {
case ServiceProcessState.Starting:
// We don't have a control endpoint in this state.
break;
case ServiceProcessState.Running:
{
// Here is where we would check for things like polling service health.
// The order of these checks would determine the priority of the requests
// being sent to the control channel.
if (service.IsAdministrativelyDisabled) {
Dbg(TraceLevel.Audit, process, "PushControlMessages - service is disabled, cannot send messages to it");
return;
}
break;
}
case ServiceProcessState.Stopping:
{
if (process.NeedSendStopControl) {
Dbg(TraceLevel.Audit, process, "PushControlMessages - sending Stop message to service.");
ServiceProcessContract.Imp:Running! svcontrol = process.GetControlEndpoint(ControlEndpointState.Stopping);
svcontrol.SendStop();
svcontrols_stopping.Add(svcontrol, process);
process.NeedSendStopControl = false;
return;
}
break;
}
}
Dbg(TraceLevel.Audit, process, "PushControlMessages - no work to do");
}
}
void PushAllServices()
{
foreach (Service! service in (!)services.Values) {
PushService(service);
}
}
/// <summary>
/// This method checks the state of a service, and generally advances that state
/// toward its goal state. It's easier and more reliable to do this in a single
/// method, and to run this method when we believe something relevant to this
/// service has happened, rather than using a lot of methods that check specific
/// aspects of the service (e.g. pushing the connect queue).
///
/// This method:
///
/// * Starts a new service process, if necessary.
/// * Dequeues connection requests and forwards them to service processes.
/// * Determines the "next think time", which is the next time that we need
/// to reexamine the state of this service, without receiving a message
/// that drives the state of the service.
///
/// This method needs to be efficient, since it is called from many places, and
/// is called at the end of many different event handlers.
/// </summary>
void PushService(Service! service)
{
Assert(service.Config.MinProcesses <= service.Config.MaxProcesses);
// If we tried to start this service before, and some problem occurred when
// trying to start a service process, then check to see if enough time has
// passed that we can clear this error.
if (service.LastStartFailed) {
SchedulerTime now = SchedulerTime.Now;
TimeSpan elapsed = now - service.LastStartFailedTime;
if (elapsed > ForgetLastStartFailedInterval) {
Dbg(TraceLevel.Audit, service, "Clearing 'last start' error.");
service.LastStartFailed = false;
service.LastStartError = ServiceError.None;
service.LastStartFailedTime = new SchedulerTime();
}
}
// Scan all of the processes for this service. Count how many are in different
// states; this is interesting for several reasons that follow. The only state
// transition that we do here is to send a Stop() message to those processes
// that are already in the Stopping state, and have not yet received a Stop()
// message. All other work per process depends on whether the service is
// administratively disabled or not.
int processes_running = 0;
int processes_running_control_ready = 0;
int processes_starting = 0;
int processes_stopping = 0;
foreach (ServiceProcess! process in service.Processes) {
switch (process.State) {
case ServiceProcessState.Starting:
processes_starting++;
break;
case ServiceProcessState.Running:
processes_running++;
if (process.ControlEndpointIsReady)
processes_running_control_ready++;
break;
case ServiceProcessState.Stopping:
processes_stopping++;
if (process.NeedSendStopControl && process.ControlEndpointIsReady) {
Dbg(TraceLevel.Audit, process, "Sending 'Stop' request to process.");
process.NeedSendStopControl = false;
ServiceProcessContract.Imp! svcontrol = process.GetControlEndpoint(ControlEndpointState.Stopping);
svcontrol.SendStop();
expose (this) { svcontrols_stopping.Add(svcontrol, process); }
}
break;
}
}
if (!service.IsAdministrativelyDisabled) {
// Set to true if we want to start a new service process.
bool want_start_process = false;
int min_processes = service.Config.MinProcesses;
switch (service.Config.ActivationMode) {
case ServiceActivationMode.AlwaysActive:
// This service is always supposed to be active.
// Check to see if there are any services running.
min_processes = Math.Min(service.Config.MinProcesses, 1);
if (processes_running < min_processes) {
if (service.Processes.Count < service.Config.MaxProcesses) {
if (processes_starting == 0) {
Dbg(TraceLevel.Audit, service, "Need more running processes for always-active service.");
want_start_process = true;
}
else {
Dbg(TraceLevel.Audit, service, "Need to start another process for always-active service, but one is already starting.");
}
}
else {
Dbg(TraceLevel.Audit, service, "Need more running processes, but total number of processes has reached maximum.");
}
}
else {
Dbg(TraceLevel.Audit, service, "Number of processes running ({0}) is >= min processes ({1})", processes_running, min_processes);
}
break;
case ServiceActivationMode.Demand:
if (service.HasConnectRequests) {
if (processes_running_control_ready != 0) {
// At least one process is immediately available for a connect request.
// No need to start another.
}
else {
if (service.Processes.Count < service.Config.MaxProcesses) {
Dbg(TraceLevel.Audit, service, "Need more processes for demand-activated service.");
want_start_process = true;
}
}
}
break;
case ServiceActivationMode.Manual:
// Management clients have control over the lifetime of this service.
// We never create processes for the service without being instructed
// by a management client.
break;
}
if (want_start_process) {
if (processes_starting != 0) {
Dbg(TraceLevel.Audit, "Want to start another process, but cannot do so right now; another process is starting.");
}
else {
StartServiceProcess(service);
}
}
PushConnectQueue(service);
}
else {
// This service is disabled.
FailAllConnectRequests(service, ErrorCode.Unknown);
foreach (ServiceProcess! process in service.Processes) {
switch (process.State) {
case ServiceProcessState.Starting:
case ServiceProcessState.Running:
process.State = ServiceProcessState.Stopping;
process.NeedSendStopControl = true;
goto case ServiceProcessState.Stopping;
case ServiceProcessState.Stopping:
if (process.NeedSendStopControl && process.ControlEndpointIsReady) {
Dbg(TraceLevel.Audit, process, "Sending 'Stop' request to process.");
process.NeedSendStopControl = false;
ServiceProcessContract.Imp! svcontrol = process.GetControlEndpoint(ControlEndpointState.Stopping);
svcontrol.SendStop();
expose (this) { svcontrols_stopping.Add(svcontrol, process); }
}
break;
}
}
}
//
// This method has now completed any necessary changes in the state of the service.
// Next, build a new ServiceStatus (actually, InternalServiceStatus), which represents
// a brief summary of the status of the service. Then we will compare that to the last
// status that we published to service status watchers. If there is any change, then
// we will update the "last published" status, notify any watchers that are read to
// receive an update, and for those that are not ready, we record the fact that they
// missed an update.
//
ServiceState service_state;
if (processes_running != 0)
service_state = ServiceState.Running;
else if (processes_starting != 0)
service_state = ServiceState.Starting;
else if (processes_stopping != 0)
service_state = ServiceState.Stopping;
else
service_state = ServiceState.Stopped;
InternalServiceStatus status;
status.State = service_state;
status.TotalActiveClients = -1;
status.ConnectQueueLength = service.ConnectQueueLength;
status.TotalActiveProcesses = service.Processes.Count;
status.ProcessId = -1;
status.LastStartFailed = service.LastStartFailed;
status.LastStartError = service.LastStartError;
if (service.Processes.Count != 0) {
// For now, return the first process.
ServiceProcess! firstProcess = (ServiceProcess!)service.Processes[0];
Process firstSystemProcess = firstProcess.Process;
if (firstSystemProcess != null && firstSystemProcess.State == ProcessState.Active) {
status.ProcessId = firstSystemProcess.Id;
}
}
//
// Compare the last and current status, field by field.
//
bool status_changed = !InternalServiceStatus.IsEqual(ref status, ref service.LastServiceStatusPublished);
if (status_changed) {
Dbg(TraceLevel.Audit, service, "Service status has changed.");
InternalServiceStatus last_status = service.LastServiceStatusPublished;
service.LastServiceStatusPublished = status;
if (last_status.State != status.State)
Dbg(TraceLevel.Audit, service, " State {0} -> {1}", ServiceEnums.ToString(last_status.State), ServiceEnums.ToString(status.State));
if (last_status.TotalActiveProcesses != status.TotalActiveProcesses)
Dbg(TraceLevel.Audit, service, " TotalActiveProcesses {0} -> {1}", last_status.TotalActiveProcesses, status.TotalActiveProcesses);
if (last_status.ConnectQueueLength != status.ConnectQueueLength)
Dbg(TraceLevel.Audit, service, " ConnectQueueLength {0} -> {1}", last_status.ConnectQueueLength, status.ConnectQueueLength);
if (last_status.ProcessId != status.ProcessId)
Dbg(TraceLevel.Audit, service, " ProcessId {0} -> {1}", last_status.ProcessId, status.ProcessId);
if (last_status.LastStartFailed != status.LastStartFailed)
Dbg(TraceLevel.Audit, service, " LastStartFailed {0} -> {1}", last_status.LastStartFailed, status.LastStartFailed);
if (last_status.LastStartError != status.LastStartError)
Dbg(TraceLevel.Audit, service, " LastStartError {0} -> {1}", ServiceEnums.ToString(last_status.LastStartError), ServiceEnums.ToString(status.LastStartError));
// Scan through the set of service watchers for this service, and
// send changes of status to each.
ServiceStatus exstatus = status.ToExchange();
foreach (ServiceWatcher! watcher in service.StatusWatchers) {
if (watcher.EndpointCanSend) {
// The watcher is ready for a message. Send the status message
// to the client, and then put the client into an endpoint map,
// so we can receive it's ack message later.
ServiceManagerContract.Exp:WaitingServiceChange! client = watcher.AcquireEndpoint();
client.SendServiceStatusChanged(exstatus, false);
expose (this) { clients_readywatch.Add(client, watcher); }
}
else {
// The watcher is not ready to receive a message. Mark the
// watcher as having missed an update, and continue. Later,
// when the watcher sends WaitStatusChange, we'll immediately
// send a service status notification.
Dbg(TraceLevel.Audit, service, "Watcher is not ready for status update. Marking as 'missed change'.");
watcher.MissedChange = true;
}
}
// If the service was starting, then there may be clients that want to be
// informed when the service is no longer starting (whether it succeeded
// or failed).
NotifyServiceManagerWatchers(ServiceManagerEventMask.AnyServiceStatus);
}
}
/// <summary>
/// This method tries to match up clients who want to connect with the service
/// to available service processes. If necessary, it will schedule the creation
/// of a new service process.
/// </summary>
void PushConnectQueue(Service! service)
{
if (!service.HasConnectRequests)
return;
if (service.IsAdministrativelyDisabled) {
Dbg(TraceLevel.Audit, service, "PushConnectQueue - service is disabled, flushing queue");
FailAllConnectRequests(service, ErrorCode.AccessDenied);
return;
}
if (service.IsDefective) {
Dbg(TraceLevel.Audit, service, "PushConnectQueue - service is currently believed to be defective, flushing queue");
FailAllConnectRequests(service, ErrorCode.Unknown);
return;
}
// We treat all of our connection requests identically, so we don't bother
// to dequeue one until we have located a service process that can handle it.
while (service.HasConnectRequests) {
ServiceProcess bestProcess = null;
// bool found_running_service = false;
bool found_starting_service = false;
foreach (ServiceProcess! candidate in service.Processes) {
if (candidate.State == ServiceProcessState.Running) {
// found_running_service = true;
if (candidate.ControlEndpointIsReady && !candidate.HasCurrentConnectDir) {
// This service process is running, and so it is a candidate.
// Check to see if this process is a better candidate (has
// a lower load level) than the previous process.
if (bestProcess != null) {
int diff = CompareServiceLoads(bestProcess, candidate);
if (diff > 0)
bestProcess = candidate;
}
else {
bestProcess = candidate;
}
}
else {
// This service is busy processing a control message.
// Keep searching for another service process, if possible.
}
}
else if (candidate.State == ServiceProcessState.Starting) {
found_starting_service = true;
}
else if (candidate.State == ServiceProcessState.Stopping) {
// ignore services in the stopping state
}
else {
// illegal state!
Util.IllegalStateReached();
}
}
if (bestProcess != null) {
ServiceProcess! process = bestProcess;
Dbg(TraceLevel.Audit, process, "PushConnectQueue - Forwarding client connection request to process");
// Dequeue the connection request.
DirectoryServiceContract.Exp:Ready! dir;
DirectoryClientInfo! dirinfo;
string! subpath;
ServiceContract.Exp:Start! channel;
SchedulerTime timeStarted;
ServiceConnectRequest! connect = service.DequeueConnectRequest();
RecycleServiceConnectRequest(connect, out dir, out dirinfo, out subpath, out channel, out timeStarted);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// Acquire the control channel.
ServiceProcessContract.Imp! svcontrol = process.GetControlEndpoint(ControlEndpointState.Connecting);
char[] in ExHeap exsubpath = (subpath != null && subpath.Length != 0) ? Bitter.FromString2(subpath) : null;
svcontrol.SendConnect(exsubpath, channel);
expose(this) { svcontrols_connecting.Add(svcontrol, process); }
process.SetCurrentConnectDir(dir, dirinfo, subpath, timeStarted);
// Progress continues when:
// we receive AckConnect from service process
// we receive NakConnect from service process
}
else {
// There are no service processes that can immediately accept the client.
// Next, we decide what to do, based on whether we found any processes in
// the running state, starting state, defective, etc.
//
// If we found *any* process in the "starting" state, then we believe:
// * that there is reason to believe that the process will start,
// * that the process will service the connect queue once started,
// * that no other processes are in the "starting" state, so we
// cannot start any new processes.
//
// In this situation, there is nothing more we can do here. Stop now.
if (found_starting_service) {
Dbg(TraceLevel.Audit, service, "PushConnectQueue - Connect request must wait for service to start.");
break;
}
// No processes are starting. Are we allowed to start any new processes?
if (service.Processes.Count >= service.Config.MaxProcesses) {
// No, cannot start any new processes. Did we fail to start a process last
// time around? If so, then we should clear the entire queue and bail.
break;
}
if (service.Processes.Count < service.Config.MaxProcesses) {
Dbg(TraceLevel.Audit, service, "PushConnectQueue - Starting new service process...");
StartServiceProcess(service);
break;
}
Dbg(TraceLevel.Audit, service, "PushConnectQueue - Cannot forward client connection request yet.");
break;
}
}
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
/// <summary>
/// Compares two services, and selects the one with the best (lightest) load level.
/// </summary>
int CompareServiceLoads(ServiceProcess! left, ServiceProcess! right)
{
return 0;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
void FailAllConnectRequests(Service! service, ErrorCode error)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
expose(this) {
while (service.HasConnectRequests) {
DirectoryServiceContract.Exp:Ready! dir;
DirectoryClientInfo! dirinfo;
string! subpath;
ServiceContract.Exp:Start! channel;
SchedulerTime timeStarted;
ServiceConnectRequest! connect = service.DequeueConnectRequest();
RecycleServiceConnectRequest(connect, out dir, out dirinfo, out subpath, out channel, out timeStarted);
Dbg(TraceLevel.Audit, dirinfo, "Connect request for service '{0}', subpath '{1}' has failed, error = {2}.",
service.ServiceName,
subpath,
SdsUtils.ErrorCodeToString(error));
dir.SendNakBind(channel, error);
dirs.Add(dir, dirinfo);
}
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
Service[]! GetServicesSnapshot()
{
if (servicesSnapshot != null)
return servicesSnapshot;
Service[]! array = new Service[((!)services.Values).Count];
services.Values.CopyTo(array, 0);
servicesSnapshot = array;
return array;
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
void InvalidateServiceSnapshot()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
servicesSnapshot = null;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
void EnumerateServiceInfos(
[Claims]ServiceManagerContract.Exp! client,
[Claims]ServiceInfo[]! in ExHeap infos,
IEnumerator/*<Service>*/! enumerator)
{
int count = 0;
while (count < infos.Length && enumerator.MoveNext()) {
Service service = (Service)enumerator.Current;
Assert(service != null);
assert service != null;
// Dbg(TraceLevel.Audit, "Enumerating service entry - " + service.ServiceName);
expose (infos[count]) {
service.GetConfig(ref infos[count].Config);
infos[count].Status = service.LastServiceStatusPublished.ToExchange();
}
count++;
}
expose (this) {
if (count > 0) {
client.SendNextServiceInfo(infos, count);
clients_enumerating.Add(client, enumerator);
}
else {
// -XXX- enumerator.Dispose();
client.SendEnumerationTerminated(infos, count);
clients.Add(client);
}
}
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
ServiceError StartServiceProcess(Service! service)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
if (service.IsAdministrativelyDisabled) {
Dbg(TraceLevel.Audit, service, "Cannot start service. It is disabled.");
return ServiceError.ServiceIsAdministrativelyDisabled;
}
if (service.IsDefective) {
Dbg(TraceLevel.Audit, service, "Cannot start a new service process. The service is currently believed to be defective.");
return ServiceError.ServiceIsDefective;
}
// Search the list of processes for this service.
// If any of them are in the Running state, then there is no need to start
// a new service process.
foreach (ServiceProcess! process in service.Processes) {
switch (process.State) {
case ServiceProcessState.Starting:
Dbg(TraceLevel.Audit, service, "Process {0} is already starting. No need to start another.", process);
return ServiceError.ServiceIsAlreadyStarting;
case ServiceProcessState.Running:
Dbg(TraceLevel.Audit, service, "Process {0} is already running. No need to start another.", process);
return ServiceError.ServiceIsAlreadyRunning;
case ServiceProcessState.Defective:
Dbg(TraceLevel.Audit, service, "Process {0} is defective. Cannot yet start another process.", process);
return ServiceError.None;
case ServiceProcessState.Stopping:
Dbg(TraceLevel.Audit, service, "Process {0} is stopping. Will ignore this process.", process);
break;
default:
throw new Exception("Invalid service process state");
}
}
if (service.Config.MaxProcesses == 0) {
Dbg(TraceLevel.Audit, service, "Configuration is busted; MaxProcesses is zero?!");
DebugStub.Break();
}
if (service.Processes.Count >= service.Config.MaxProcesses) {
Dbg(TraceLevel.Audit, service, "This service has reached its maximum process count.");
Dbg(TraceLevel.Audit, service, "No more service processes can be started at this time.");
return ServiceError.ServiceIsAlreadyRunning;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
Dbg(TraceLevel.Audit, service, "Attempting to start new service process.");
ServiceProcess! process = new ServiceProcess(service);
process.State = ServiceProcessState.Starting;
ServiceStarter! starter;
ServiceStarterContract.Imp:Starting! notifier = ServiceStarter.StartService(process, out starter);
expose(this) { service_starters.Add(notifier, starter); }
service.Processes.Add(process);
return ServiceError.None;
// Further progress will occur when the ServiceStarter indicates that it has
// finished its request, either successfully or unsuccessfully. We receive
// this indication as a message on the ServiceStarterContract channel.
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
/// <summary>
2008-11-17 18:29:00 -05:00
/// Create the inital set of services that are specified in the system manifest.
2008-03-05 09:52:00 -05:00
/// </summary>
2008-11-17 18:29:00 -05:00
void CreateInitialServices(string[]! list)
{
for (int i = 1; i < list.Length - 2; i += 3) {
string serviceName = list[i];
string executableName = list[i + 1];
string activationModeText = list[i + 2];
if (serviceName == null || serviceName.Length == 0) {
Dbg(TraceLevel.Audit, "The initial service list contains an entry with null service name.");
continue;
}
if (executableName == null || executableName.Length == 0) {
Dbg(TraceLevel.Audit, "The initial service list contains an entry with null binary name. The service name is '{0}'.", serviceName);
continue;
}
if (activationModeText == null || activationModeText.Length == 0) {
Dbg(TraceLevel.Audit, "The initial service list contains an entry with a null 'activationMode' value. The service name is '{0}'.", serviceName);
continue;
}
InternalServiceConfig config;
config.ServiceName = serviceName;
config.ExecutableName = executableName;
config.DisplayName = serviceName;
config.IsAdministrativelyDisabled = false;
config.MinProcesses = 0;
config.MaxProcesses = 1;
config.MaxClientsPerProcess = -1;
config.MaxProcessAgeInSeconds = -1;
activationModeText = activationModeText.ToLower();
if (activationModeText == "demand") {
config.ActivationMode = ServiceActivationMode.Demand;
config.MinProcesses = 0;
}
else if (activationModeText == "manual") {
config.ActivationMode = ServiceActivationMode.Manual;
config.MinProcesses = 0;
}
else if (activationModeText == "alwaysactive") {
config.ActivationMode = ServiceActivationMode.AlwaysActive;
config.MinProcesses = 1;
}
else {
Dbg(TraceLevel.Audit, "Warning: The initial service entry '{0}' has an invalid activation mode of '{1}'.", serviceName, activationModeText);
Dbg(TraceLevel.Audit, "Setting the mode to Demand.");
config.ActivationMode = ServiceActivationMode.Demand;
}
if (services.Contains(serviceName)) {
Dbg(TraceLevel.Audit, "A service named '{0}' already exists. Ignoring duplicate entry.", serviceName);
continue;
}
ServiceError error = CreateService(config);
if (error != ServiceError.None) {
Dbg(TraceLevel.Audit, "Error: Failed to create service '{0}': {1}", serviceName, ServiceEnums.ToString(error));
continue;
}
}
}
ServiceError StopService(Service! service)
{
switch (service.Config.ActivationMode) {
case ServiceActivationMode.Manual:
case ServiceActivationMode.Demand:
2008-03-05 09:52:00 -05:00
break;
2008-11-17 18:29:00 -05:00
case ServiceActivationMode.AlwaysActive:
Dbg(TraceLevel.Audit, service, "This service uses the 'always active' activation mode, and so cannot be stopped.");
return ServiceError.CannotStopService;
}
if (service.Processes.Count == 0) {
Dbg(TraceLevel.Audit, service, "Cannot stop service; it is not running.");
return ServiceError.ServiceIsNotRunning;
}
foreach (ServiceProcess! process in service.Processes) {
switch (process.State) {
case ServiceProcessState.Running:
case ServiceProcessState.Starting:
process.State = ServiceProcessState.Stopping;
process.NeedSendStopControl = true;
break;
case ServiceProcessState.Stopping:
break;
}
}
FailAllConnectRequests(service, ErrorCode.Unknown);
return ServiceError.None;
}
void TerminateServiceProcess(ServiceProcess! process)
{
switch (process.State) {
case ServiceProcessState.Running:
case ServiceProcessState.Starting:
case ServiceProcessState.Stopping:
2008-03-05 09:52:00 -05:00
break;
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
default:
2008-11-17 18:29:00 -05:00
throw new Exception("Invalid state");
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
Process system_process = process.Process;
if (system_process != null) {
Dbg(TraceLevel.Warning, process, "Terminating service process.");
system_process.Join(TimeSpan.FromSeconds(2));
system_process.Stop();
system_process.Join();
}
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
void ScanForStoppedProcesses()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
foreach (Service! service in (!)services.Values) {
ScanServiceForStoppedProcesses(service);
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
void ScanServiceForStoppedProcesses(Service! service)
{
bool any_stopped = false;
int i = 0;
while (i < service.Processes.Count) {
ServiceProcess! process = (ServiceProcess!)service.Processes[i];
if (process.State == ServiceProcessState.Stopping) {
Process system_process = process.Process;
if (system_process != null && system_process.State == ProcessState.Stopped) {
Dbg(TraceLevel.Audit, process, "Process has terminated.");
process.ReleaseProcess();
service.Processes.RemoveAt(i);
any_stopped = true;
// Do NOT increment i here.
continue;
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
i++;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
if (any_stopped)
PushService(service);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
static bool StringIsNullOrEmpty(string s)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
return s == null || s.Length == 0;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
ServiceError CreateService(InternalServiceConfig config)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
if (StringIsNullOrEmpty(config.ServiceName)
|| StringIsNullOrEmpty(config.DisplayName)
|| StringIsNullOrEmpty(config.ExecutableName))
{
Dbg(TraceLevel.Audit, "Cannot create service. Required parameters are missing.");
return ServiceError.InvalidArguments;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
if (config.MinProcesses < 0) {
Dbg(TraceLevel.Audit, "Cannot create service. MinProcesses value is invalid (negative).");
return ServiceError.InvalidArguments;
}
if (config.MaxProcesses <= 0) {
Dbg(TraceLevel.Audit, "Cannot create service. MaxProcesses value is invalid (negative or zero).");
return ServiceError.InvalidArguments;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
if (config.MinProcesses > config.MaxProcesses) {
Dbg(TraceLevel.Audit, "Cannot create service. MinProcesses value is greater than MaxProcesses.");
return ServiceError.InvalidArguments;
}
if (config.MaxClientsPerProcess != -1 && config.MaxClientsPerProcess <= 0) {
Dbg(TraceLevel.Audit, "Cannot create service. MaxClientsPerProcess must be -1 (disabled), or must be greater than zero.");
return ServiceError.InvalidArguments;
}
switch (config.ActivationMode) {
case ServiceActivationMode.AlwaysActive:
case ServiceActivationMode.Demand:
case ServiceActivationMode.Manual:
break;
default:
Dbg(TraceLevel.Audit, "Cannot create service. ActivationMode is invalid.");
return ServiceError.InvalidArguments;
}
string! serviceName = config.ServiceName;
Service service = FindService(serviceName);
if (service != null) {
Dbg(TraceLevel.Audit, "Cannot create service. Service '{0}' already exists.");
return ServiceError.ServiceExists;
}
service = new Service(config);
// service.LastServiceStatusPublished = service.GetStatus();
services[serviceName] = service;
Dbg(TraceLevel.Audit, service, "Service successfully created.");
PushService(service);
return ServiceError.None;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
Service FindService(string! name)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
Service service = (Service)services[name];
return service;
}
#region Diagnostics
internal static string! MakeDbgPrefix(string! context)
{
return String.Format("SMS[{0,-20}] ", context);
}
internal static void TraceLog(TraceLevel level, string! line)
{
if (level >= tracePrintLevel) {
DebugStub.WriteLine(line);
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
// DebugStub.Break();
Tracing.Log((byte)level, line);
}
static void Dbg(TraceLevel level, string! line)
{
TraceLog(level, line);
}
static void Dbg(TraceLevel level, Service! service, string! line)
{
TraceLog(level, service.dbgprefix + line);
}
static void Dbg(TraceLevel level, ServiceProcess! process, string! line)
{
TraceLog(level, process.dbgprefix + line);
}
static void Dbg(TraceLevel level, DirectoryClientInfo! dirinfo, string! line)
{
TraceLog(level, dirinfo.dbgprefix + line);
}
#region Variants of Dbg that use String.Format
static void Dbg(TraceLevel level, string! format, params object[]! args)
{
Dbg(level, String.Format(format, args));
}
static void Dbg(TraceLevel level, Service! service, string! format, params object[]! args)
{
Dbg(level, service, String.Format(format, args));
}
static void Dbg(TraceLevel level, ServiceProcess! process, string! format, params object[]! args)
{
Dbg(level, process, String.Format(format, args));
}
static void Dbg(TraceLevel level, DirectoryClientInfo! dirinfo, string! format, params object[]! args)
{
Dbg(level, dirinfo, String.Format(format, args));
}
#endregion
static void DumpException(Exception! ex)
{
for (Exception focus = ex; focus != null; focus = focus.InnerException) {
Dbg(TraceLevel.Error, "{0}: {1}", ex.GetType().FullName, ex.Message);
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
void AssertIntegrity()
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
foreach (DictionaryEntry entry in services) {
Assert(entry.Key != null, "entry.Key != null");
Assert(entry.Value != null, "entry.Value != null");
string name = (string)entry.Key;
Service service = (Service)entry.Value;
Assert(name != null, "name != null");
Assert(service != null, "service != null");
assert service != null;
assert name != null;
if (name != service.ServiceName)
assert false;
// assert name == service.ServiceName;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
foreach (ServiceStartWatcher! startWatcher in serviceStartWatchers) {
Service service = FindService(startWatcher.Service.ServiceName);
assert service != null;
assert service == startWatcher.Service;
}
foreach (ServiceStopWatcher! stopWatcher in serviceStartWatchers) {
Service service = FindService(stopWatcher.Service.ServiceName);
assert service != null;
assert service == stopWatcher.Service;
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
void Assert(bool condition, string! msg)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
if (condition)
return;
Dbg(TraceLevel.Error, "Assertion failed: " + msg);
DebugStub.Break();
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
void Assert(bool condition)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
if (condition)
return;
Dbg(TraceLevel.Error, "Assertion failed.");
DebugStub.Break();
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
#endregion
ServiceConnectRequest! AllocateServiceConnectRequest(
[Claims]DirectoryServiceContract.Exp:Ready! dir,
DirectoryClientInfo! dirinfo,
string! subpath,
[Claims]ServiceContract.Exp:Start! channel,
SchedulerTime timeStarted)
{
ServiceConnectRequest request;
if (connectRequestLookasideCount > 0) {
connectRequestLookasideCount--;
request = connectRequestLookasideList[connectRequestLookasideCount];
assert request != null;
request.Release(dir, dirinfo, subpath, channel, timeStarted);
return request;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
else {
request = new ServiceConnectRequest(dir, dirinfo, subpath, channel, timeStarted);
return request;
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
void RecycleServiceConnectRequest(
ServiceConnectRequest! request,
out DirectoryServiceContract.Exp:Ready! dir,
out DirectoryClientInfo! dirinfo,
out string! subpath,
out ServiceContract.Exp:Start! channel,
out SchedulerTime timeStarted)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
request.Acquire(out dir, out dirinfo, out subpath, out channel, out timeStarted);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
// need to assert that request.Acquire() has been called
if (connectRequestLookasideCount < connectRequestLookasideList.Length) {
connectRequestLookasideList[connectRequestLookasideCount] = request;
connectRequestLookasideCount++;
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
else {
request.Dispose();
2008-03-05 09:52:00 -05:00
}
}
2008-11-17 18:29:00 -05:00
#region ITracked methods
void ITracked.Expose() {}
void ITracked.UnExpose() {}
void ITracked.Acquire() {}
void ITracked.Release() {}
#endregion
}
/// <summary>
/// Instances of this class are used to associate information with instances of
/// DirectoryServiceContract.Exp. The Service Manager allows clients to connect
/// to '/service/services' using DirectoryServiceContract. The Service Manager only
/// supports a few requests: service enumeration, and binding to services.
/// </summary>
class DirectoryClientInfo
{
public DirectoryClientInfo(string! path)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
this.Path = path;
this.dbgprefix = ServiceManager.MakeDbgPrefix("dir '" + path + "'");
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
public string! Path;
public string! dbgprefix;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
#if false
/// <summary>
/// Used while enumerating a particular directory.
/// </summary>
public IEnumerator/*<Service>*/ Enumerator;
#endif
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
class ServiceManagerWatcher
{
public ServiceManagerWatcher()
{
endpointIsReady = false;
endpointRef = null;
EventsMissed = 0;
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
public ServiceManagerEventMask DesiredEvents;
public ServiceManagerEventMask EventsMissed;
private bool endpointIsReady;
private TRef<ServiceManagerContract.Exp:WaitingServiceManagerChange> endpointRef;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
public void SetEndpoint([Claims]ServiceManagerContract.Exp:WaitingServiceManagerChange! ep)
{
if (endpointIsReady)
throw new Exception("This ServiceManagerWatcher instance already has an endpoint.");
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
if (endpointRef != null)
endpointRef.Release(ep);
else
endpointRef = new TRef<ServiceManagerContract.Exp:WaitingServiceManagerChange>(ep);
endpointIsReady = true;
EventsMissed = 0;
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
public ServiceManagerContract.Exp:WaitingServiceManagerChange! GetEndpoint()
{
if (!endpointIsReady)
throw new Exception("This ServiceManagerWatcher instance does not have an endpoint.");
assert endpointRef != null;
ServiceManagerContract.Exp:WaitingServiceManagerChange! ep = endpointRef.Acquire();
endpointIsReady = false;
return ep;
}
public bool IsEndpointReady
{
get { return endpointIsReady; }
}
public void Dispose()
{
if (endpointIsReady) {
ServiceManagerContract.Exp:WaitingServiceManagerChange! ep = GetEndpoint();
delete ep;
}
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
}
/// <summary>
/// Keep these values in sync with the Tracing levels (System.Tracing).
/// </summary>
enum TraceLevel : byte
{
Fatal = 0xe, // system crashed.
Error = 0xc, // system will crash.
Warning = 0xa, // cause for immediate concern.
Notice = 0x8, // might be cause for concern.
Trace = 0x6, // may be of use in crash.
Audit = 0x4, // impact on performance.
Debug = 0x2, // used only for debugging.
2008-03-05 09:52:00 -05:00
}
2008-11-17 18:29:00 -05:00
2008-03-05 09:52:00 -05:00
}