Universalmediaserver: "Prevent Windows From Sleeping" keeps computer awake even if not streaming

Created on 22 Jun 2015  路  21Comments  路  Source: UniversalMediaServer/UniversalMediaServer

This may be by design but it would be nice to have the option of only
keeping the computer awake while a moving is being streamed.

--Neil

All 21 comments

It would be a nice feature to have :)

@SubJunk now it is implemented to prevent Windows from sleeping when any renderer or any other device like router in the network is communicating with the UMS otherwise the sleeping is after 40 seconds re-enabled. Maybe this is not a good concept because all living devices are preventing Windows to sleep just when they are sending any message.

I am sure there is a logical bug in the WinUtils.java:

   @Override
   public void disableGoToSleep() {
      // Disable go to sleep (every 40s)
      if (configuration.isPreventsSleep() && System.currentTimeMillis() - lastDontSleepCall > 40000) {
         LOGGER.trace("Calling SetThreadExecutionState ES_SYSTEM_REQUIRED");
         Kernel32.INSTANCE.SetThreadExecutionState(Kernel32.ES_SYSTEM_REQUIRED | Kernel32.ES_CONTINUOUS);
         lastDontSleepCall = System.currentTimeMillis();
      }
   }

   @Override
   public void reenableGoToSleep() {
      // Reenable go to sleep
      if (configuration.isPreventsSleep() && System.currentTimeMillis() - lastGoToSleepCall > 40000) {
         LOGGER.trace("Calling SetThreadExecutionState ES_CONTINUOUS");
         Kernel32.INSTANCE.SetThreadExecutionState(Kernel32.ES_CONTINUOUS);
         lastGoToSleepCall = System.currentTimeMillis();
      }
   }

The "if" prevents calling SetThreadExecutionState faster than every 40 seconds. That's a good idea but there are situations where it has to be called more often! For instance:

  1. disableGoToSleep is called (streaming started)
  2. after 1 minute reenableGoToSleep is called (streaming finished)
  3. a few seconds later disableGoToSleep is called (streaming is started again)
  4. a few seconds later reenableGoToSleep is called (streaming finished again)

The second disableGoToSleep will be executed but the second reenableGoToSleep will not! Even though the streaming is finished the PC will not go to sleep anymore.
You can also find examples where a needed disableGoToSleep is suppressed.

I would suggest to change the conditions in the "if" clauses:

   @Override
   public void disableGoToSleep() {
      // Disable go to sleep (every 40s)
      if (configuration.isPreventsSleep() && (System.currentTimeMillis() - lastDontSleepCall > 40000) || (lastGoToSleepCall >= lastDontSleepCall)) {
         LOGGER.trace("Calling SetThreadExecutionState ES_SYSTEM_REQUIRED");
         Kernel32.INSTANCE.SetThreadExecutionState(Kernel32.ES_SYSTEM_REQUIRED | Kernel32.ES_CONTINUOUS);
         lastDontSleepCall = System.currentTimeMillis();
      }
   }

   @Override
   public void reenableGoToSleep() {
      // Reenable go to sleep
      if (configuration.isPreventsSleep() && (System.currentTimeMillis() - lastGoToSleepCall > 40000) || (lastDontSleepCall >= lastGoToSleepCall)) {
         LOGGER.trace("Calling SetThreadExecutionState ES_CONTINUOUS");
         Kernel32.INSTANCE.SetThreadExecutionState(Kernel32.ES_CONTINUOUS);
         lastGoToSleepCall = System.currentTimeMillis();
      }
   }

Please scroll to right to see my modifications in the message above.

@scriptorron, could you make a pull request for that?

Universal Media Server is still blocking windows from going to sleep.

@ErdnussFlipS, where can I find a short description how to do this? I am familiar with CSV and SVN but Github seems to be completely different. I found a description how to check out an UMS working copy with git but there was no word about the other direction. Can anyone help me to get my modifications back into the repository?

In September 2016 I downloaded a working copy of the source code and modified srcmainjavanetpmsioWinUtils.java:

    @Override
    public void disableGoToSleep() {
        LOGGER.debug("Called disableGoToSleep");
        if (configuration.isPreventsSleep() && System.currentTimeMillis() - lastDontSleepCall > 40000) {
            LOGGER.debug("Calling SetThreadExecutionState ES_SYSTEM_REQUIRED");
            Kernel32.INSTANCE.SetThreadExecutionState(Kernel32.ES_SYSTEM_REQUIRED);
            lastDontSleepCall = System.currentTimeMillis();
        }
    }

    @Override
    public void reenableGoToSleep() {
        LOGGER.debug("Called reenableGoToSleep");
    }

The patched "disableGoToSleep" retriggers the windows idle timer (like if you shake the mouse). See https://msdn.microsoft.com/de-de/library/windows/desktop/aa373208(v=vs.85).aspx for detailed documentation. I set the idle timer in the windows energy options to 20 minutes. As long as "disableGoToSleep" is called within this time my computer does not go to sleep. After the last call of "disableGoToSleep" it takes 20 minutes until Windows goes to power down. The "reenableGoToSleep" is not needed any more.
My modification is working since 6 months without any problems. I use it as UPnP server on a Win 10 PC (it also runs the MediaPortal server which has no UPnP support). An Android tablet with BubbleUPnP works as control and a Samsung BlueRay player as renderer. The PC with UMS wakes up automatically (wake-over-LAN) and goes to sleep when I stopped listening music.
I use it only for playing music and all my titles are shorter than 20 minutes. I am not sure if "disableGoToSleep" is called often enough when playing longer titles (like movies).

@ErdnussFlipS I described the process somewhat here and here. There are also loads of web pages that explains how to do it if you search.

The solution won't work as a general solution even if it works for you, the "timer reset" won't be sent during movie playback (it's only sent when starting playback) - so it would require users to have their sleep times set higher than the highest content length they'd like to be able to play.

@Nadahar, for my opinion the problem is that the timer reset must be done at the right network requests. I do not know so much about UPnP and DLNA but I can imagine that large content is transmitted in smaller chunks at request of the receiver. The time between these requests will be shorter than 20 minutes. I would accept that the server goes to sleep if the playback is stopped or in pause for a longer period of time.
It is not easy to decide which UPnP/DLNA requests should retrigger the timer. A discovery request ("Is there a server in the network?") should for my opinion not retrigger the timer.
In a parallel thread a reference counter is proposed which counts up when a new playback starts and down when a playback ends. But how to detect that a playback has ended? We can not relay on cooperation of the other network partners - the connection can get lost at any time!

I still think that the sleep timer is the best solution.

Thank you for your GitHub guide. I forgot to "fork". As soon as I find enough spare time I will try it again.

@scriptorron Things are handled on many levels here - some in the network hardware, some in the drivers, some in the OS, some in JVM, some in libraries used by UMS and then some in the actual UMS code. While I agree that things are probably sent in smaller chunks (or via other form of "flow control") somewhere under the hood, that's now how it looks from UMS' point of view. We simply hand out a "stream" of data to a given HTTP connection and it is then transfered in its own pace. There is something called chunked transfer which some renderers use, but this is not the normal.

As a consequence relying on "chunk requests" to maintain the no-sleep state isn't viable. UMS does however track when a playback ends. We have several things that need to be handled when that happens (like shutting down transcoding engines where that apply), so we need to track it anyhow. The currently implemented reenableGoToSleep() is called from there AFAICR. I don't think this is the core of the problem.

I think the problem is that in some scenarios multiple start/stop events occur, and this isn't coordinated in any way. The timing might not be how you'd imagine it, so I suspect that the commands to the OS sometimes are sent in the wrong order. Since they aren't coordinated the last command sent is always the one in effect. Using multiple renderers means this have to go wrong, if you start a video on one renderer, play a short clip or an audio file on another, sleep mode will be re-enabled when the clip or audio file ends - even though the video on the other renderer is still playing.

This is why I said that I think a reference counter is the way to go.

@Nadahar, I see what you mean. Today I have more time to get my git problems solved and to to some experiments. I can confirm that my solution with the idle timer is not retriggered when playing long content.
I also checked your idea with the reference counter but found some problems:

When I connect and disconnected the BubbleUPnP client on a fresh started UMS I get 11 calls of "disableGoToSleep" and only 8 calls of "reenableGoToSleep". BubbleUPnP is completely shut down and there is no other client active on UMS. The RequestHandlerV2 seems to miss the finalisation of some requests! The disable is called near the beginning of RequestHandlerV2 right after rejecting self-originated requests. The reenable is called 2 times in "RequestV2.java" but not at all exit points.

The other "RequestHandler" is doing this right but calls "disableGoToSleep" and "reenableGoToSleep" on "false" requests (at least at all self originating requests). Windows will never go to sleep with the "RequestHandler"!

I will focus on "RequestHandlerV2". Maybe find something.

I implemented a (hopefully) thread save counter to find out if a disableGoToSleep and reenableGoToSleep are needed or not. Furthermore I moved the reenableGoToSleep from "RequestV2.java" to "RequestHandlerV2.java" to make sure that each disable has a reenable. The debug log shows now the same numbers of effective disables and reenables. But I still have 2 issues:

1.) Streaming of large content seems to run in a parallel thread (for instance I see ffmpeg in the log) which is not between a disable and a reenable! There is still work needed to avoid sleep during streaming!

2.) Even with the same number of SetThreadExecutionState calls to disable and reenable I still see wrong results with "powercfg --requests" (run this as administrator on the command line). This happens when disable and reenable come from different "New I/O worker" threads and the reenable is only a few micro-seconds after the disable. Probably I have to forbit thread switching when disableGoToSleep and reenableGoToSleep are running. Maybe "synchronized" may help.

Unfortunately I run out of time. Is anyone interested of what I have done? If yes, how do I get it back to GitHub? I am not sure if a "pull request" is the right way to do this because my work is not fully completed.

Your changes can be accessed in your cloned repository on Github, in the dedicated branch (PreventWindowsFromSleep_fix I suppose).
If you have pushed all your local changes in the your Github hosted repository, all is already visible and for now no need to submit a pull request.

cf. https://github.com/UniversalMediaServer/UniversalMediaServer/compare/master...scriptorron:PreventWindowsFromSleep_fix

@scriptorron A pull request is useful even if you don't intent do actually merge it as is. It gives us easy access to the code and a place to discuss it.

That said, I've created #1226, as it has so much talk about sleep prevention lately. I really don't have the time to do this now, but it looked easier to do that than to try to explain to everyone what I mean. It's just a draft/concept for now though, there are many things I haven't done. For one I haven't looked at where this gets called at all, and that obviously need to be correct. Your code might come in handy there.

I've been in doubt all the time as to how SetThreadExecutionState really works. Most references I've seen have referred to it as if it is a per-process call. Microsoft doesn't say, even though thread is in the name. I now think that it's a per-thread call. That means that the sleep prevention and the cancelling must come from the same thread, and this requires a different design.

One possibility would be to just set "prevent sleep" for every thread that starts playback, and then trust that Windows kept track of when the threads are terminated and invalidates its status. It would be very easy to implement, but it has a couple of drawbacks: 1) It's hard to know when a thread ceases to exist in the OS' perspective. All we can do in Java is to stop it, but the instance still exists. Stopping it is probably not enough to clear it's status, so we'd need for it to be destroyed. However, Java doesn't give us such "low level" control and we have to wait for the GC to do that. We have no control over when the GC runs, and it can be a considerable amount of time later. 2) The implementation for other OS'es like OS X will have to be very different. It's difficult to generalize the code when using such a OS-specific logic.

I've therefore come to the conclusion that the best way is to spawn a separate thread that handles this. All calls will be made from this thread only, the other threads will simply tell it when to take action.

The way the HTTP servers we use work, is that they spawn a new thread for every request. That means the calls will almost never be from the same thread, and the whole scheme fails.

Thank you! I sent a pull request "PreventWindowsFromSleep_fix" - maybe it has some value for you. I found that it is easier and safer to let Windows count the disable and reenable calls.

@Nadahar, you are right, SetThreadExecutionState is per thread. I need some time to think about the other things you said.

(Sorry for my bad English, I am not a native speaker.)

I just realized that I pulled the wrong version. Will fix this in a few minutes!

@scriptorron Your English seems just fine to me, but I'm not native either 馃槈

Have you looked at my solution? I'm currently rewriting it to use a single thread right now, but it will take a bit of time before that is pushed.

@Nadahar, I answered in #1226. I am not sure if I can test your solution within the next days (have to travel).

@nradisch @SamyCookie @scriptorron @ErdnussFlipS Do you want test this @Nadahar "sleep" version ?
https://we.tl/Ku5vts3y7e ( _Windows_ EXE and JAR file for _Java 8_)

Feedback will be much appreciated ;)
Thanks.

Here's a download link for OS X (Java 8) if anyone wants to test that as well.

This should be fixed in #1226.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mbentley picture mbentley  路  8Comments

SubJunk picture SubJunk  路  3Comments

SubJunk picture SubJunk  路  8Comments

yohonet picture yohonet  路  4Comments

Nadahar picture Nadahar  路  4Comments