Serviço não totalmente parado após ServiceController.Stop ()

votos
5
ServiceController serviceController = new ServiceController(someService);
serviceController.Stop();
serviceController.WaitForStopped();
DoSomething();

SomeService funciona em um arquivo sqlserver. DoSomething () quer copiar esse arquivo SQL. Se SomeService não está fechada totalmente vai lançar um erro porque o arquivo de banco de dados ainda está bloqueado. No código acima mencionado, eu ter passado o método WaitForStopped () e ainda o serviço não libera o arquivo de banco de dados até depois DoSomething (), portanto, eu recebo um erro.

Fazendo mais algumas investigações, acho que antes de chamar o método DoSomething vejo que o status controlador de serviço mostra um parou e ainda olhar para alguns ProcMon registra o serviço libera o arquivo de banco de dados depois que eu estou jogado um erro de DoSomething.

Além disso, se eu colocar um Thread.Sleep entre o WaitForStopped eo método DoSomething para dizer ... 5 segundos, o arquivo de banco de dados é liberado e está tudo bem. Não a solução de garantia que eu estou procurando no entanto.

Alguma ideia?

Publicado 19/05/2009 em 21:28
fonte usuário
Em outras línguas...                            


5 respostas

votos
17

Windows Services são uma camada em cima dos processos; a fim de ser um serviço, um aplicativo deve se conectar ao Service Control Manager e anunciar quais serviços estão disponíveis. Esta conexão é tratado dentro da biblioteca ADVAPI32.DLL. Uma vez que esta conexão é estabelecida, a biblioteca mantém uma linha à espera de comandos a partir do Service Control Manager, que pode, então, iniciar e parar serviços de forma arbitrária. Eu não acredito que o processo é necessário para sair quando o último serviço em que ele termina. No entanto, que é o que normalmente acontece, o fim do vínculo com o Service Control Manager, que ocorre após o último serviço entra no estado "parado", pode ocorrer significativamente antes que o processo realmente termina, liberando os recursos ainda não o tenha liberado explicitamente .

O API de serviço do Windows inclui uma funcionalidade que lhe permite obter o ID do processo do processo que hospeda o serviço. É possível para um único processo para hospedar muitos serviços, e assim o processo não pode realmente sair quando o serviço que você está interessado em ter terminado, mas você deve ser seguro com o SQL Server. Infelizmente, o .NET Framework não expõe essa funcionalidade. É, no entanto, expor o identificador para o serviço que ele usa internamente para chamadas de API, e você pode usá-lo para fazer suas próprias chamadas de API. Com um pouco de P / Invoke, então, você pode obter o ID do processo do processo serviço do Windows, e de lá, desde que tenha a autorização necessária, você pode abrir um identificador para o processo que pode ser usado para esperar por ele para Saída.

Algo assim:

[DllImport("advapi32")]
static extern bool QueryServiceStatusEx(IntPtr hService, int InfoLevel, ref SERVICE_STATUS_PROCESS lpBuffer, int cbBufSize, out int pcbBytesNeeded);

const int SC_STATUS_PROCESS_INFO = 0;

[StructLayout(LayoutKind.Sequential)]
struct SERVICE_STATUS_PROCESS
{
  public int dwServiceType;
  public int dwCurrentState;
  public int dwControlsAccepted;
  public int dwWin32ExitCode;
  public int dwServiceSpecificExitCode;
  public int dwCheckPoint;
  public int dwWaitHint;
  public int dwProcessId;
  public int dwServiceFlags;
}

const int SERVICE_WIN32_OWN_PROCESS = 0x00000010;

const int SERVICE_RUNS_IN_SYSTEM_PROCESS = 0x00000001;

public static void StopServiceAndWaitForExit(string serviceName)
{
  using (ServiceController controller = new ServiceController(serviceName))
  {
    SERVICE_STATUS_PROCESS ssp = new SERVICE_STATUS_PROCESS();
    int ignored;

    // Obtain information about the service, and specifically its hosting process,
    // from the Service Control Manager.
    if (!QueryServiceStatusEx(controller.ServiceHandle.DangerousGetHandle(), SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(ssp), out ignored))
      throw new Exception("Couldn't obtain service process information.");

    // A few quick sanity checks that what the caller wants is *possible*.
    if (ssp.dwServiceType != SERVICE_WIN32_OWN_PROCESS)
      throw new Exception("Can't wait for the service's hosting process to exit because there may be multiple services in the process (dwServiceType is not SERVICE_WIN32_OWN_PROCESS");

    if ((ssp.dwServiceFlags & SERVICE_RUNS_IN_SYSTEM_PROCESS) != 0)
      throw new Exception("Can't wait for the service's hosting process to exit because the hosting process is a critical system process that will not exit (SERVICE_RUNS_IN_SYSTEM_PROCESS flag set)");

    if (ssp.dwProcessId == 0)
      throw new Exception("Can't wait for the service's hosting process to exit because the process ID is not known.");

    // Note: It is possible for the next line to throw an ArgumentException if the
    // Service Control Manager's information is out-of-date (e.g. due to the process
    // having *just* been terminated in Task Manager) and the process does not really
    // exist. This is a race condition. The exception is the desirable result in this
    // case.
    using (Process process = Process.GetProcessById(ssp.dwProcessId))
    {
      // EDIT: There is no need for waiting in a separate thread, because MSDN says "The handles are valid until closed, even after the process or thread they represent has been terminated." ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms684868%28v=vs.85%29.aspx ), so to keep things in the same thread, the process HANDLE should be opened from the process id before the service is stopped, and the Wait should be done after that.

      // Response to EDIT: What you report is true, but the problem is that the handle isn't actually opened by Process.GetProcessById. It's only opened within the .WaitForExit method, which won't return until the wait is complete. Thus, if we try the wait on the current therad, we can't actually do anything until it's done, and if we defer the check until after the process has completed, it won't be possible to obtain a handle to it any more.

      // The actual wait, using process.WaitForExit, opens a handle with the SYNCHRONIZE
      // permission only and closes the handle before returning. As long as that handle
      // is open, the process can be monitored for termination, but if the process exits
      // before the handle is opened, it is no longer possible to open a handle to the
      // original process and, worse, though it exists only as a technicality, there is
      // a race condition in that another process could pop up with the same process ID.
      // As such, we definitely want the handle to be opened before we ask the service
      // to close, but since the handle's lifetime is only that of the call to WaitForExit
      // and while WaitForExit is blocking the thread we can't make calls into the SCM,
      // it would appear to be necessary to perform the wait on a separate thread.
      ProcessWaitForExitData threadData = new ProcessWaitForExitData();

      threadData.Process = process;

      Thread processWaitForExitThread = new Thread(ProcessWaitForExitThreadProc);

      processWaitForExitThread.IsBackground = Thread.CurrentThread.IsBackground;
      processWaitForExitThread.Start(threadData);

      // Now we ask the service to exit.
      controller.Stop();

      // Instead of waiting until the *service* is in the "stopped" state, here we
      // wait for its hosting process to go away. Of course, it's really that other
      // thread waiting for the process to go away, and then we wait for the thread
      // to go away.
      lock (threadData.Sync)
        while (!threadData.HasExited)
          Monitor.Wait(threadData.Sync);
    }
  }
}

class ProcessWaitForExitData
{
  public Process Process;
  public volatile bool HasExited;
  public object Sync = new object();
}

static void ProcessWaitForExitThreadProc(object state)
{
  ProcessWaitForExitData threadData = (ProcessWaitForExitData)state;

  try
  {
    threadData.Process.WaitForExit();
  }
  catch {}
  finally
  {
    lock (threadData.Sync)
    {
      threadData.HasExited = true;
      Monitor.PulseAll(threadData.Sync);
    }
  }
}
Respondeu 20/10/2009 em 21:55
fonte usuário

votos
3

ServiceController.WaitForStopped () / WaitForStatus () irá retornar uma vez que a implementação do serviço afirma que parou. Isso não significa necessariamente o processo lançou todos os seus recursos e saiu. Eu vi banco de dados diferente do SQL Server fazer isso também.

Se você realmente quer ter certeza de banco de dados é plena e verdadeiramente parado, você terá a interface com o próprio banco de dados, costuma ficar o ID do processo e esperar por ele para sair, esperar por bloqueios nos arquivos a serem liberados, .. .

Respondeu 19/05/2009 em 22:00
fonte usuário

votos
1

No meu caso eu usei os interops:

[StructLayout(LayoutKind.Sequential)]
public struct SC_HANDLE__
{
    public int unused;
}

[Flags]
public enum SERVICE_CONTROL : uint
{
    STOP = 0x00000001,
    PAUSE = 0x00000002,
    CONTINUE = 0x00000003,
    INTERROGATE = 0x00000004,
    SHUTDOWN = 0x00000005,
    PARAMCHANGE = 0x00000006,
    NETBINDADD = 0x00000007,
    NETBINDREMOVE = 0x00000008,
    NETBINDENABLE = 0x00000009,
    NETBINDDISABLE = 0x0000000A,
    DEVICEEVENT = 0x0000000B,
    HARDWAREPROFILECHANGE = 0x0000000C,
    POWEREVENT = 0x0000000D,
    SESSIONCHANGE = 0x0000000E
}

[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS
{

    /// DWORD->unsigned int
    public uint dwServiceType;
    /// DWORD->unsigned int
    public uint dwCurrentState;
    /// DWORD->unsigned int
    public uint dwControlsAccepted;
    /// DWORD->unsigned int
    public uint dwWin32ExitCode;
    /// DWORD->unsigned int
    public uint dwServiceSpecificExitCode;
    /// DWORD->unsigned int
    public uint dwCheckPoint;
    /// DWORD->unsigned int
    public uint dwWaitHint;
}

public class NativeMethods
{
    public const int SC_MANAGER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED
                | (SC_MANAGER_CONNECT
                | (SC_MANAGER_CREATE_SERVICE
                | (SC_MANAGER_ENUMERATE_SERVICE
                | (SC_MANAGER_LOCK
                | (SC_MANAGER_QUERY_LOCK_STATUS | SC_MANAGER_MODIFY_BOOT_CONFIG))))));

    /// STANDARD_RIGHTS_REQUIRED -> (0x000F0000L)
    public const int STANDARD_RIGHTS_REQUIRED = 983040;
    /// SC_MANAGER_CONNECT -> 0x0001
    public const int SC_MANAGER_CONNECT = 1;
    /// SC_MANAGER_CREATE_SERVICE -> 0x0002
    public const int SC_MANAGER_CREATE_SERVICE = 2;
    /// SC_MANAGER_ENUMERATE_SERVICE -> 0x0004
    public const int SC_MANAGER_ENUMERATE_SERVICE = 4;
    /// SC_MANAGER_LOCK -> 0x0008
    public const int SC_MANAGER_LOCK = 8;
    /// SC_MANAGER_QUERY_LOCK_STATUS -> 0x0010
    public const int SC_MANAGER_QUERY_LOCK_STATUS = 16;
    /// SC_MANAGER_MODIFY_BOOT_CONFIG -> 0x0020
    public const int SC_MANAGER_MODIFY_BOOT_CONFIG = 32;
    /// SERVICE_CONTROL_STOP -> 0x00000001
    public const int SERVICE_CONTROL_STOP = 1;
    /// SERVICE_QUERY_STATUS -> 0x0004
    public const int SERVICE_QUERY_STATUS = 4;
    public const int GENERIC_EXECUTE = 536870912;
    /// SERVICE_RUNNING -> 0x00000004
    public const int SERVICE_RUNNING = 4;

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW")]
    public static extern IntPtr OpenSCManagerW(
        [In()] [MarshalAs(UnmanagedType.LPWStr)] string lpMachineName,
        [In()] [MarshalAs(UnmanagedType.LPWStr)] string lpDatabaseName,
        uint dwDesiredAccess);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ControlService(IntPtr hService, SERVICE_CONTROL dwControl, ref SERVICE_STATUS lpServiceStatus);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseServiceHandle(IntPtr hSCObject);

    [DllImport("advapi32.dll", EntryPoint = "QueryServiceStatus", CharSet = CharSet.Auto)]
    public static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS dwServiceStatus);

    [SecurityCritical]
    [HandleProcessCorruptedStateExceptions]
    public static void ServiceStop()
    {
        IntPtr manager = IntPtr.Zero;
        IntPtr service = IntPtr.Zero;
        SERVICE_STATUS status = new SERVICE_STATUS();

        if ((manager = OpenSCManagerW(null, null, SC_MANAGER_ALL_ACCESS)) != IntPtr.Zero)
        {
            if ((service = OpenService(manager, Resources.ServiceName, SC_MANAGER_ALL_ACCESS)) != IntPtr.Zero)
            {
                QueryServiceStatus(service, ref status);
            }

            if (status.dwCurrentState == SERVICE_RUNNING)
            {
                int i = 0;
                //not the best way, but WaitStatus didnt work correctly. 
                while (i++ < 10 && status.dwCurrentState != SERVICE_CONTROL_STOP)
                {
                    ControlService(service, SERVICE_CONTROL.STOP, ref status);
                    QueryServiceStatus(service, ref status);
                    Thread.Sleep(200);
                }

            }
        }


        if (manager != IntPtr.Zero)
        {
            var b = CloseServiceHandle(manager);
        }

        if (service != IntPtr.Zero)
        {
            var b = CloseServiceHandle(service);
        }
    }
}
Respondeu 13/02/2014 em 13:37
fonte usuário

votos
0

Eu já vi isso antes, quando eu parei um serviço que era uma dependência para outro serviço, e o segundo serviço estava segurando recursos que eu nem sabia que ele estava usando. Você acha que isso pode ser o caso? Sei SQL tem alguns componentes diferentes, mas eu não olhei para se há vários serviços associados.

Boa sorte!

Respondeu 19/05/2009 em 21:41
fonte usuário

votos
-4

Tentar usar Environment.Exit (1);

Respondeu 19/05/2009 em 21:40
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more