ID:79955
 
BYOND Version:453
Operating System:Windows XP Home
Web Browser:Firefox 3.5.2
Applies to:Dream Daemon
Status: Deferred

This issue may be low priority or very difficult to fix, and has been put on the back burner for the time being.
Descriptive Problem Summary:
shutdown() does not return until the DreamDaemon instance is manually closed.

Numbered Steps to Reproduce Problem:
  1. Open a game in DreamDaemon through startup()
  2. shutdown() on the return value of startup()


Code Snippet (if applicable) to Reproduce Problem:
var/running

mob/verb
StartWorld()
if(!running)
running = startup(file("../COCommand/COCommand.dmb"), 4712)
src << "[running ? "The world has been successfully hosted at [running]." : "Failed to host world."]"
StopWorld()
if(running)
src << "Trying to stop world..."
shutdown(running)
src << "World should be down now"
running = null
else
src << "There is currently no word running."


Expected Results:
When invoking StopWorld(), it should display, "Trying to stop world...", then shut down the running game, then display "World should be down now".

Actual Results:
When calling StopWorld(), "Trying to stop world..." is displayed and the verb stops there, until you manually close the DreamDaemon instance, which is when "World should be down now" is displayed.

Does the problem occur:
Every time? Or how often? Everytime
In other user accounts? N/A
On other computers? N/A

Workarounds:
Unknown.
Just as additional note, the hosted world's Topic() had not been overridden, so it should lead to the default, desired result.
running was tried both as normal return value, as well as text:
running = "[startup(file("../COCommand/COCommand.dmb"), 4712)]"

Edit:
The reason seems to be that the Master flag is not set.

Server:
mob/verb
RebootWorld()
if(running)
src << "Sending reboot command."
src << "World returned: [world.Export("byond://[running]?Reboot")]"
else
src << "You are not currently running a server. Sorry."


Client-world:
world
Topic(T, Addr, Master, Key)
switch(T)
if("Reboot")
if(Master)
world.Reboot(2)
return "Successfully rebooted server."
else
return "Sorry, you do not have permission for this."
else
. = ..()


This returns: "Sorry, you do not have permission for this."
My guess is that the soft-wired default implementation of Del does not return 0 when the Masterflag is 0, like it should to prevent locking the Export up, which would explain shutdown() to silently lock up.

Edit2:
world
Topic(T, Addr, Master, Key)
switch(T)
if("Del")
if(Master || (Addr == world.address))
world << "The world is being closed in 1 second."
spawn(10)
world.Del()
return "Successfully rebooted server."
else
return "Sorry, you do not have permission for this."
else
. = ..()

Closes the world, but shutdown() still does not return unless the DreamDaemon instance is closed manually.
This is a known issue from a while back; I've been investigating (it's difficult to do as it involves two processes so the second can't run in a debugger very easily) and it seems to be that the master world isn't being identified by the right IP address or port at the time of the child's startup.
Lummox JR wrote:
(...)the master world isn't being identified by the right IP address or port at the time of the child's startup.

This is rather confusing me.
I tried to shutdown() the world and the procedure did not crash, but instead simply failed to return. However, when manually closing the DreamDaemon instance, the running process returned.
So, now that would indicate the procedure successfully manages to connect to the DreamDaemon instance (else it could not react upon it closing).
If there is a 'simple' authentication fail, I would have assumed the procedure to return instantly (and I know I might very well be rather wrong here, not knowing the source code).

Does shutdown() not operate through world.Export()?

If it does, there might be some interesting observations here:
For once, startup returns [world.address]:[world.port], rather than [world.internet_address]:[world.port]
Furthermore, when running the startupserver locally in DS and using world.Export, the Addr received by topic is just world.address, but when running the startupserver in DreamDaemon, [world.address]:[world.port] is the Addr argument.
shutdown() operates through partially the same mechanism as world.Export(), but also partially not. It does send the "Del" message, but it does not return after the message is sent; it waits for the app to shutdown, as noted in the report.

I broke this down into three issues yesterday.

1) The child world thinks its master address is "127.0.0.1", but that's not the address it receives in world/Topic() which instead is world.address. I consider this a true bug, but it was an easy fix. This is now fixed for version 454.

2) The child world thinks its master world's port is whatever port was used to contact it, if the master world is not hosted. If the master world is hosted, then the correct port is used. Ultimately this means that if the master world is not hosted, the child won't recognize it as its master, because the child world thinks "192.168.0.100:1803" or such is the address and world/Topic() is given "192.168.0.100" with no port instead. This is a bit slipperier of a problem and I have yet to come up with an adequate solution. With the fix to #1 above though this will work if the master world has an open port.

3) Because shutdown() waits for the child world to close--behavior that I actually think is good--this needs to be modified in some way for Windows. In Linux, shutting down a child world will end that DD instance automatically, but in Windows it does not; theoretically the fix for #1 should already take care of Linux-hosted worlds because #2 isn't an issue for them. Therefore there are three options I can see for dealing with this. First, I could add another command line switch to DD so it knwos it should shutdown when the server closes. Second, the child world could send some kind of message to its master to let it know the shutdown was successful. Third, I can use signals (if Windows will support it) to close the child process. The use of signals was actually proposed at one point either by Tom or Dan, as the problem with issue #2 was noted directly in the shutdown() code.

There is a workaround to this issue, albeit a convoluted one. The master world can send its child a message that sets up a random "key" used for confirming its masterhood, which the child would accept as long as such a message hadn't come in before. Then if that key is sent during any future messages, it can be used as a basis for accepting that the world contacting it is its master. To shut it down then you'd send a message manually instead of calling shutdown().
Lummox JR wrote:
The child world thinks its master address is "127.0.0.1", but that's not the address it receives in world/Topic() which instead is world.address.

I wonder why startup even returns a string with [world.address]:[world.port], as, if I'm not completely wrong, you can only spawn off new DD instances on the same machine anyway.
If this is the case, would it not make more sense for startup to return [world.internet_address]:[world.port]&[hash_password].
hash_password would ensure that no other DD instance on that machine could call shutdown and shutdown itself should simply go for localhost:port anyway.


Lummox JR wrote:
2) The child world thinks its master world's port is whatever port was used to contact it, if the master world is not hosted. If the master world is hosted, then the correct port is used. Ultimately this means that if the master world is not hosted, the child won't recognize it as its master, because the child world thinks "192.168.0.100:1803" or such is the address and world/Topic() is given "192.168.0.100" with no port instead. This is a bit slipperier of a problem and I have yet to come up with an adequate solution. With the fix to #1 above though this will work if the master world has an open port.

This would be solved through the suggested hash_password, as the identification of the master world would not rely on port, but on a) coming from localhost and b) including the hash.


Lummox JR wrote:
3) Because shutdown() waits for the child world to close--behavior that I actually think is good--this needs to be modified in some way for Windows.

I agree that shutdown should wait for the child to close, but I would assume/request for shutdown to return immediately, as 0, if the request was rejected (like it is the case in this bug-report), to indicate it's failure, instead of just locking up the thread.


Lummox JR wrote:
There is a workaround to this issue, albeit a convoluted one. The master world can send its child a message that sets up a random "key" used for confirming its masterhood, which the child would accept as long as such a message hadn't come in before. Then if that key is sent during any future messages, it can be used as a basis for accepting that the world contacting it is its master. To shut it down then you'd send a message manually instead of calling shutdown().

I could not figure how you'd set and send the key. Mind to provide a short snippet, since world.Export does not seem to take any such argument (or it is not mentioned in the reference).
Schnitzelnagler wrote:
I wonder why startup even returns a string with [world.address]:[world.port], as, if I'm not completely wrong, you can only spawn off new DD instances on the same machine anyway.
If this is the case, would it not make more sense for startup to return [world.internet_address]:[world.port]&[hash_password].
hash_password would ensure that no other DD instance on that machine could call shutdown and shutdown itself should simply go for localhost:port anyway.

I'm not sure such a change would be compatible with existing games. Also since & isn't valid in an address, that format wouldn't make any sense; I think if such a thing was used it would have to be done internally.

Lummox JR wrote:
3) Because shutdown() waits for the child world to close--behavior that I actually think is good--this needs to be modified in some way for Windows.

I agree that shutdown should wait for the child to close, but I would assume/request for shutdown to return immediately, as 0, if the request was rejected (like it is the case in this bug-report), to indicate it's failure, instead of just locking up the thread.

That seems sensible, but I don't know if it's possible. AFAIK the Del message doesn't return a success code, so this would not be backwards-compatible.

Lummox JR wrote:
There is a workaround to this issue, albeit a convoluted one. The master world can send its child a message that sets up a random "key" used for confirming its masterhood, which the child would accept as long as such a message hadn't come in before. Then if that key is sent during any future messages, it can be used as a basis for accepting that the world contacting it is its master. To shut it down then you'd send a message manually instead of calling shutdown().

I could not figure how you'd set and send the key. Mind to provide a short snippet, since world.Export does not seem to take any such argument (or it is not mentioned in the reference).

I'm referring to sending a custom message to include this info. You'd just pass it as a parameter like any other.
Lummox JR wrote:
Also since & isn't valid in an address, that format wouldn't make any sense;

I meant for parsing with pramams2list ;)


Lummox JR wrote:
I think if such a thing was used it would have to be done internally.

I agree. This would certainly be the preferred solution, though, as for compability reasons, you can always add another argument to startup that defaults to zero when not sent, to trigger the new format.


Lummox JR wrote:
I'm referring to sending a custom message to include this info. You'd just pass it as a parameter like any other.

Oh, I was under the impression you'd be referring to the key argument mentioned in world.Topic.
After updating to 454.1036, the child world still does not get the Masterflag set when hosting through Dream Daemon for me. I'm running Windows XP Home Edition.
Any advice or chance of a live debugging session?
Strange enough, it is working just fine for me under Windows XP Home on 454.1036.
BUMP + Some Notes:
The master world only gets set if it is online when it starts the other server. Which makes sense, but that should probably be noted in the help files.
Even if you do set the master, shutdown() still doesn't work worth a crap (as noted in the other posts here).
You can work around this by using a Del topic(), however, that doesn't close the Dream Daemon. Which means if you're using this for any sensible purpose (like a remote server manager) you end up with 30,000 DD instances floating around in your task bar.

Also on a semi-related note: startup() returns the local IP if you're on a router.
Falacy wrote:
BUMP + Some Notes:
The master world only gets set if it is online when it starts the other server. Which makes sense, but that should probably be noted in the help files.
Even if you do set the master, shutdown() still doesn't work worth a crap (as noted in the other posts here).
You can work around this by using a Del topic(), however, that doesn't close the Dream Daemon. Which means if you're using this for any sensible purpose (like a remote server manager) you end up with 30,000 DD instances floating around in your task bar.

Also on a semi-related note: startup() returns the local IP if you're on a router.

Yeah, I was using it for a remote server manager, and using Del() did leave multiple instances of DD open. I thought they would have fixed this by now, I made this report like a year ago.
With the recent feature addition of the -close parameter for Dream Daemon, I'm moving this issue to Deferred.

The only problem remaining that I think is significant is that if the master world is not hosted, the child doesn't recognize it as its master. That remains non-trivial to solve.