///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: Services\ServiceManager\Service.sg
//
// Note: Service counterpart
//
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Applications;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.ServiceManager;
using Microsoft.Singularity.V1.Processes;
using Microsoft.Singularity.Xml;
namespace Microsoft.Singularity.Services.ServiceManager
{
///
/// This class represents the state of a service, as seen by the Service Manager.
///
/// Threading: Instances of this class are owned by the main (entry point) thread.
/// That is the only thread that creates instances of this class, and reads/writes
/// its fields.
///
/// There is one important exception to consider: The main thread uses the ServiceStarter
/// class to create and start service processes, and when it does so, the main thread
/// passes a reference to an instance of Service to ServiceStarter. ServiceStarter never
/// examines the contents of the instance; it passes it back to the main thread.
/// This maintains the invariant that only the main thread can read/write fields.
///
/// This class encapsulates this information:
/// * the complete configuration of the service,
/// * current information about the status of the service,
/// * references to all of the processes that have been created for this service,
/// * state fields that control state transitions (starting, stopping, etc.)
/// * state fields relevant to detection of defective service processes and recovery.
///
/// This class provides methods for reading/writing some of the state of a Service
/// instance, but in general, this class does not contain much of the semantics of the
/// Service Manager. The Service class is not an independent, standalone object; it is
/// a data structure owned and manipulated by the ServiceManager class. The methods
/// defined on this class handle making controlled state transitions (such as acquiring
/// an endpoint); the methods do *not* make global or broadly-scoped state transitions.
/// That is the responsibility of the ServiceManager class.
///
class Service
{
public Service(InternalServiceConfig config)
{
this.ServiceName = config.ServiceName;
this.Config = config;
this.dbgprefix = String.Format("SMS[{0,-20}] ", config.ServiceName);
this.NextThinkTime = Util.SchedulerTimeNever;
InternalServiceStatus status;
status.State = ServiceState.Stopped;
status.TotalActiveClients = -1;
status.TotalActiveProcesses = 0;
status.ConnectQueueLength = 0;
status.ProcessId = -1;
status.LastStartFailed = false;
status.LastStartError = ServiceError.None;
this.LastServiceStatusPublished = status;
}
#region Data Fields
public string! dbgprefix;
#region Configuration
public readonly string! ServiceName;
public string[] DependentServices;
public bool IsAdministrativelyDisabled { get { return Config.IsAdministrativelyDisabled; } }
public InternalServiceConfig Config;
#endregion
public readonly ArrayList/**/! Processes = new ArrayList();
public bool IsMarkedForDeletion;
public bool IsDeleted;
public bool IsDefective;
public bool LastStartFailed;
public ServiceError LastStartError;
public SchedulerTime LastStartFailedTime;
public SchedulerTime NextThinkTime;
///
/// This is the last service status that we have published to management clients that are
/// watching service status. When there is any possibility that the status of a service
/// has changed, we build a new ServiceStatus object, then compare it to this field.
/// If any field has changed, then we notify all service watchers.
///
public InternalServiceStatus LastServiceStatusPublished;
public bool CanStartServiceProcess()
{
if (this.IsAdministrativelyDisabled)
return false;
if (this.Processes.Count >= this.Config.MaxProcesses)
return false;
if (LastStartFailed)
return false;
return true;
}
///
/// Contains instances of ServiceConnectRequest, representing clients that want
/// to connect to a service.
///
private readonly Queue/**/! connectQueue = new Queue();
public readonly ArrayList/**/! StatusWatchers = new ArrayList();
#endregion
public void EnqueueConnectRequest(
[Claims]DirectoryServiceContract.Exp:Ready! dir,
DirectoryClientInfo! dirinfo,
string! subpath,
[Claims]ServiceContract.Exp:Start! channel,
SchedulerTime timeStarted)
{
ServiceConnectRequest! connect = new ServiceConnectRequest(dir, dirinfo, subpath, channel, timeStarted);
connectQueue.Enqueue(connect);
}
public void EnqueueConnectRequest(ServiceConnectRequest! connect)
{
connectQueue.Enqueue(connect);
}
public bool HasConnectRequests
{
get { return connectQueue.Count > 0; }
}
public int ConnectQueueLength
{
get { return connectQueue.Count; }
}
public ServiceConnectRequest! DequeueConnectRequest()
{
if (connectQueue.Count == 0)
throw new InvalidOperationException("There are no requests in the connect queue for this service.");
ServiceConnectRequest! request = (ServiceConnectRequest!)connectQueue.Dequeue();
return request;
}
public void GetConfig(ref ServiceConfig config)
{
expose(config)
{
delete config.DisplayName;
delete config.ExecutableName;
delete config.ServiceName;
this.Config.ToExchangeType(out config);
#if false
config.ServiceName = Bitter.FromString2(this.ServiceName);
config.DisplayName = Bitter.FromString2(this.DisplayName);
config.ExecutableName = Bitter.FromString2(this.ExecutableName);
config.IsAdministrativelyDisabled = this.IsAdministrativelyDisabled;
config.MinProcesses = this.MinProcesses;
config.MaxProcesses = this.MaxProcesses;
config.MaxClientsPerProcess = this.MaxClientsPerProcess;
config.MaxProcessAgeInSeconds = this.MaxProcessAgeInSeconds;
#endif
}
}
}
// represents a client who is attempting to connect to a service
// the service cannot process the request immediately.
// either the service is starting, or its control channel is busy, etc.
class ServiceConnectRequest
{
public ServiceConnectRequest(
[Claims]DirectoryServiceContract.Exp:Ready! dir,
DirectoryClientInfo! dirinfo,
string! subpath,
[Claims]ServiceContract.Exp:Start! channel,
SchedulerTime timeStarted)
{
this.subpath = subpath;
this.dirref = new TRef(dir);
this.channelref = new TRef(channel);
this.dirinfo = dirinfo;
this.timeStarted = timeStarted;
}
public void Acquire(
out DirectoryServiceContract.Exp:Ready! dir,
out DirectoryClientInfo! dirinfo,
out string! subpath,
out ServiceContract.Exp:Start! channel,
out SchedulerTime timeStarted)
{
dir = this.dirref.Acquire();
dirinfo = this.dirinfo;
subpath = this.subpath;
channel = this.channelref.Acquire();
timeStarted = this.timeStarted;
}
public void Release(
[Claims]DirectoryServiceContract.Exp:Ready! dir,
DirectoryClientInfo! dirinfo,
string! subpath,
[Claims]ServiceContract.Exp:Start! channel,
SchedulerTime timeStarted)
{
this.dirref.Release(dir);
this.subpath = subpath;
this.channelref.Release(channel);
this.timeStarted = timeStarted;
this.dirinfo = dirinfo;
}
readonly TRef! dirref;
readonly TRef! channelref;
string! subpath;
DirectoryClientInfo! dirinfo;
SchedulerTime timeStarted;
public void Dispose()
{
// dirref.Dispose();
// channelref.Dispose();
}
}
struct InternalServiceStatus
{
public ServiceStatus ToExchange()
{
ServiceStatus status;
status.State = this.State;
status.TotalActiveClients = this.TotalActiveClients;
status.TotalActiveProcesses = this.TotalActiveProcesses;
status.ConnectQueueLength = this.ConnectQueueLength;
status.ProcessId = this.ProcessId;
status.LastStartError = this.LastStartError;
status.LastStartFailed = this.LastStartFailed;
return status;
}
public ServiceState State;
public int TotalActiveClients;
public int TotalActiveProcesses;
public int ConnectQueueLength;
public long ProcessId;
public bool LastStartFailed;
public ServiceError LastStartError;
public static bool IsEqual(ref InternalServiceStatus left, ref InternalServiceStatus right)
{
return left.State == right.State
&& left.TotalActiveClients == right.TotalActiveClients
&& left.TotalActiveProcesses == right.TotalActiveProcesses
&& left.ConnectQueueLength == right.ConnectQueueLength
&& left.ProcessId == right.ProcessId
&& left.LastStartFailed == right.LastStartFailed
&& left.LastStartError == right.LastStartError;
}
public static bool IsEqual(InternalServiceStatus left, InternalServiceStatus right)
{
return IsEqual(ref left, ref right);
}
}
///
/// This structure is a "local" (in same process) equivalent to the exchangeable ServiceConfig type.
/// This is necessary because we can't store rep structures in local types, and because we want to use
/// System.String, not char[] in ExHeap for our strings.
///
struct InternalServiceConfig
{
public string! ServiceName;
public string! DisplayName;
public string! ExecutableName;
public ServiceActivationMode ActivationMode;
public bool IsAdministrativelyDisabled;
public int MinProcesses;
public int MaxProcesses;
public int MaxClientsPerProcess;
public int MaxProcessAgeInSeconds;
public InternalServiceConfig(ref ServiceConfig config)
{
expose (config)
{
this.ServiceName = Bitter.ToString2(config.ServiceName);
this.DisplayName = Bitter.ToString2(config.DisplayName);
this.ExecutableName = Bitter.ToString2(config.ExecutableName);
this.ActivationMode = config.ActivationMode;
this.IsAdministrativelyDisabled = config.IsAdministrativelyDisabled;
this.MinProcesses = config.MinProcesses;
this.MaxProcesses = config.MaxProcesses;
this.MaxClientsPerProcess = config.MaxClientsPerProcess;
this.MaxProcessAgeInSeconds = config.MaxProcessAgeInSeconds;
}
}
public ServiceConfig ToExchangeType()
{
ServiceConfig config = new ServiceConfig();
config.ServiceName = Bitter.FromString2(this.ServiceName);
config.DisplayName = Bitter.FromString2(this.DisplayName);
config.ExecutableName = Bitter.FromString2(this.ExecutableName);
config.ActivationMode = this.ActivationMode;
config.IsAdministrativelyDisabled = this.IsAdministrativelyDisabled;
config.MinProcesses = this.MinProcesses;
config.MaxProcesses = this.MaxProcesses;
config.MaxClientsPerProcess = this.MaxClientsPerProcess;
config.MaxProcessAgeInSeconds = this.MaxProcessAgeInSeconds;
return config;
}
public void ToExchangeType(out ServiceConfig config)
{
config = new ServiceConfig();
// expose (config) {
delete config.DisplayName;
delete config.ExecutableName;
delete config.ServiceName;
config.ServiceName = Bitter.FromString2(this.ServiceName);
config.DisplayName = Bitter.FromString2(this.DisplayName);
config.ExecutableName = Bitter.FromString2(this.ExecutableName);
config.ActivationMode = this.ActivationMode;
config.IsAdministrativelyDisabled = this.IsAdministrativelyDisabled;
config.MinProcesses = this.MinProcesses;
config.MaxProcesses = this.MaxProcesses;
config.MaxClientsPerProcess = this.MaxClientsPerProcess;
config.MaxProcessAgeInSeconds = this.MaxProcessAgeInSeconds;
// }
}
}
}