Skip to content

proposal: x/sys/windows/svc: Call AllocConsole as part of Run #72884

Open

Description

@mediocregopher

Proposal Details

Background

I am implementing a windows service which act as a kind of process manager; based on user input during its lifetime the service process will spawn its own child processes (with the CREATE_NEW_PROCESS_GROUP flag set), and based on other user input the service may decide to shut down those child processes using windows.GenerateConsoleCtrlEvent(syscall.CTRL_BREAK_EVENT, pid). This control event acts more or less like a SIGINT in windows world.

When running the process as an actual service, via the Windows Service Manager, I use svc.Run to make all the necessary system calls and process control messages.

When running the service from console the GenerateConsoleCtrlEvent works fine. However, when being run as a service, the GenerateConsolCtrlEvent returns the error The handle is invalid.. This is apparently because when running a process as a service windows does not allocate a console for the process.

Adding the following into my Handler's Execute method fixes the problem:

if r, _, err := windows.NewLazySystemDLL("kernel32.dll").NewProc("AllocConsole").Call(); r == 0 {
	panic(err)
}

Loading the DLL manually is necessary because AllocConsole is not exposed via x/sys/windows.

I discovered that AllocConsole would solve this issue based on this 20 year old thread from CodeGuru.

Proposal

I propose that svc.Run should call AllocConsole itself, perhaps just after the call to RegisterServiceCtrlHandlerEx. I am not a professional windows developer so I could well be mistaken, but I don't believe there's any reason to not allocate a console, and doing so would save other users from having to dig up this odd requirement in the future.

If there is a reason why someone would not want their service to have a console attached then perhaps a new function, RunOpt, could be introduced to the package. Something like:

type RunOpts struct {
    AllocConsole bool
}

func RunOpt(name string, handler Handler, opts RunOpts) error

The AllocConsole field could specifically document why a console allocation would be desired, which would expose this possible edge-case to the user.

Secondary Proposal: Expose AllocConsole in x/sys/windows

I don't know what the process is for adding new syscalls to x/sys/windows, but having AllocConsole exposed there would be convenient.

Activity

  1. added this to the Proposal milestone on Mar 15, 2025
  2. added
    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a tool
    on Mar 15, 2025
  3. ianlancetaylor commented on Mar 16, 2025

    @ianlancetaylor
    Contributor

    CC @golang/windows

  4. alexbrainman commented on Mar 16, 2025

    @alexbrainman
    Member

    Why you cannot run AllocConsole API before you start your service?

    I propose that svc.Run should call AllocConsole itself, perhaps just after the call to RegisterServiceCtrlHandlerEx. I am not a professional windows developer so I could well be mistaken, but I don't believe there's any reason to not allocate a console, and doing so would save other users from having to dig up this odd requirement in the future.

    I think what you are doing is pretty unusual, because average windows service does not interact with logged in user. Whole point of the service is to run without user logged in.

    But I am not a Windows expert enough to decide if adding AllocConsole to the begging of this code is a problem or not. Perhaps other users will help.

    If there is a reason why someone would not want their service to have a console attached then perhaps a new function, RunOpt, could be introduced to the package.

    I don't think this feature is important enough to warrant new option that every user need to understand. But others might disagree.

    I don't know what the process is for adding new syscalls to x/sys/windows, but having AllocConsole exposed there would be convenient.

    You don't need a proposal. If you want to add AllocConsole Windows API to the x/sys/windows package, just send a change.

    Alex

  5. mediocregopher commented on Mar 16, 2025

    @mediocregopher
    Author

    Why you cannot run AllocConsole API before you start your service?

    I can do so, and that is my solution at the moment, but it's not an obvious thing to have to do, and I thought if there was no downside to the svc package doing it automatically it would save folks some headache in the future.

    I think what you are doing is pretty unusual, because average windows service does not interact with logged in user. Whole point of the service is to run without user logged in.

    Users programs interact with the service via an RPC interface. This is fairly standard, based on both MSDN docs and questions people ask online 0 1 2 3. The unusual part is that the service is spawning and managing its own child processes, which is something I've found very little precedent for, except for that one thread I linked in the OP.

    I don't think this feature is important enough to warrant new option that every user need to understand. But others might disagree.

    I also don't know if this warrants an API change, which is why I listed it as a secondary option. IMO the package calling AllocConsole automatically would be much more preferable, if there's no downside to doing so.

    You don't need a proposal. If you want to add AllocConsole Windows API to the x/sys/windows package, just send a change.

    Sounds good, will do so :)

  6. alexbrainman commented on Mar 17, 2025

    @alexbrainman
    Member

    I can do so, and that is my solution at the moment, ...

    Good.

    but it's not an obvious thing to have to do, and I thought if there was no downside to the svc package doing it automatically it would save folks some headache in the future.

    You are the first who complained about this problem. So your situation is not very common. I don't think that adding more code will "improve" the situation - new code needs to be maintained by the Go team.

    Alex

  7. moved this to Incoming in Proposalson Mar 19, 2025
  8. ssexton71 commented on Mar 26, 2025

    @ssexton71

    As a longtime Windows dev who has written several services, I will tell you this: the usual way to control services like you are trying to do is not to emulate SIGINT but to use a Win32 Event (CreateEvent/SetEvent) both when running as a service AND when running as a console. C style events (SIGINT, etc) are second class citizens in Windows. You will be much happier with the Win32 IPC primitives: Events, Mutexes, etc.

  9. mediocregopher commented on Mar 27, 2025

    @mediocregopher
    Author

    @ssexton71 unfortunately the child services I'm working with are not themselves written for windows, but for unix (but with windows support). So they don't set up windows event handlers like you describe, but do respond to the C-style events, so that's the avenue I'm taking to send them a simple shutdown message.

  10. ssexton71 commented on Mar 27, 2025

    @ssexton71

    That is your choice ofc. I was just trying to add some color since there seemed to be some q as to whether your approach based on Unix signal handlers is typical of a Windows service. It isn't. CreateEvent/SetEvent/WaitForMultipleObjects is. Idk if WFMO plays nice with goroutines though, since it blocks the thread. Note this isnt the same as a WIndows GUI event loop. It is more like a semaphore with a max value of 1. So not a lot to set up but it will tie up a thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolOS-WindowsProposal

    Type

    No type

    Projects

    • Status
      Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions