Jump to content




[1.75] OS dropping timers



6 replies to this topic

#1 Jetpack_Maniac

  • Members
  • 5 posts

Posted 12 July 2017 - 10:17 PM

I'm experiencing a problem in all versions of ComputerCraft I've used.

Versions: CC 1.75 and 1.80pr0 and 1.80pr11. (I understand the new devs builds are unstable, however, the bug wasn't version dependent, I believe it exists within the OS VM itself.)

Description: Any code that takes longer than 1 tick (0.05 seconds) to execute will cause all other timers to drop. This includes if the timer was scheduled to execute after the 1 tick delay. (e.g. a 1 tick computer action will cause timers 1 full second after the completed action finished to drop). I also have the timers recurse, however, only 1 timer will

Expected Result: The computer to not drop timers.

Reproduction Instructions: To demonstrate the circumstances I had, I wrote a simple proof of concept. It's available here, https://pastebin.com/TrZhvhBn. I create a list of timers and insert them into a map storing the timer ID as the key and the ordinal number of the timer as the value. Running the program allows you to reproduce the problem I had.

I've included controls to see the results in real time. If you enable the delay, you'll notice an immediate drop off of all but one timer. If you disable the delay, the lone timer will still be the only one. All timers will return once the delay is disabled and the map is rebuilt.

I'm not very good at Java but I will also take a look at the code and see if I can identify the potential issue in the mod. Thank you for your time.

#2 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 6,571 posts
  • LocationTasmania (AU)

Posted 13 July 2017 - 12:40 AM

Looking at your example code, I see you're calling os.sleep(0.5) at line 53. Here's the source for that function:

function sleep( nTime )
    if nTime ~= nil and type( nTime ) ~= "number" then
        error( "bad argument #1 (expected number, got " .. type( nTime ) .. ")", 2 ) 
    end
    local timer = os.startTimer( nTime or 0 )
    repeat
        local sEvent, param = os.pullEvent( "timer" )
    until param == timer
end

As you can see, running this loop after pulling your first timer will merrily eat up the other three you set before allowing your script to continue further. Note also that new timers have a minimum expiry time of "the next server tick", so even requesting a sleep delay of 0 would be enough to ensure that that all your other timers would be pulled here first.

In fact, any time you call any function that "pauses" (eg rednet.receive(), turtle.forward(), sleep(), various functions offered by third-party peripherals...), that function will be pulling events to do it, and odds are it'll be pulling and then simply discarding other events that you might've wanted to pull elsewhere in your script during the process.

The solution is to either not call those functions (manage the events they use within your own code instead), or to call them from within a different coroutine (eg via the parallel API).

Edited by Bomb Bloke, 13 July 2017 - 12:49 AM.


#3 Jetpack_Maniac

  • Members
  • 5 posts

Posted 13 July 2017 - 02:51 AM

View PostBomb Bloke, on 13 July 2017 - 12:40 AM, said:

The solution is to either not call those functions (manage the events they use within your own code instead), or to call them from within a different coroutine (eg via the parallel API).

while running do
	parallel.waitForAny(handleTimer, humanInteraction)
end

I already am using the parallel API in the sample I had posted. I thought the sleep function might get a lot of attention since it calls an event itself. Here's the original sample I was using where I discovered this problem. Sleep is NOT called. The computer is busy using open peripherals to move items and the same problem exists.

https://github.com/j.../autobee_cc.lua

For reference:
Timer handler: https://github.com/j...bee_cc.lua#L106
Function ran when timer triggers: https://github.com/j...eeCore.lua#L239

I've since changed the original map scheme. What I wanted to do was a staggered check apiaries. The OS dropping all timers made that not possible to implement as is though. I don't mind creating my own code for things but if I notice a shortcoming, I'm going to bring it forward so we can fix it. I use the original scheme for the Open Computers version and it does not have any issues.

Edited by Jetpack_Maniac, 13 July 2017 - 03:37 AM.


#4 SquidDev

  • Members
  • 1,036 posts
  • LocationDoes anyone put something serious here?

Posted 13 July 2017 - 07:00 AM

View PostBomb Bloke, on 13 July 2017 - 12:40 AM, said:

In fact, any time you call any function that "pauses" (eg rednet.receive(), turtle.forward(), sleep(), various functions offered by third-party peripherals...), that function will be pulling events to do it, and odds are it'll be pulling and then simply discarding other events that you might've wanted to pull elsewhere in your script during the process.
In this case, checkApiary calls checkOutput which in turn calls getItemMeta or getStackInSlot. Both these functions will run code on the main server thread, causing the computer thread to yield and gobble all events on the queue. You can see this in a bit more detail by adding a logging system to the parallel loop.

parallel.waitForAny(
  function()
	while running do
		  parallel.waitForAny(handleTimer, handlePeripheralAttach, handlePeripheralDetach, humanInteraction)
	end
  end,
  function()
	while true do
		  print(os.pullEvent())
	end
  end
)

You should see lots of timer and task_complete events, the latter coming from peripherals.

I'm not as familiar with OpenComputer's internals as much as I am with CC's, but I believe their peripherals do not depend on the event queue (I think they just block the computer from executing). Consequently sleep events are not discarded.

Edited by SquidDev, 13 July 2017 - 07:13 AM.


#5 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 6,571 posts
  • LocationTasmania (AU)

Posted 13 July 2017 - 01:34 PM

View PostJetpack_Maniac, on 13 July 2017 - 02:51 AM, said:

I already am using the parallel API in the sample I had posted.

Sure you are, but you're not using it to bundle off your sleep() call into a different coroutine. You're calling sleep() from within the same coroutine where you're also wanting to monitor other events, and hence you're going to get interference.

The idea is that every coroutine parallel manages for you gets passed a copy of every event your main script is getting resumed with (bearing in mind that that's running inside a coroutine too - in ComputerCraft, all your code is). The code within each coroutine can pull and discard as many events as it likes without affecting the code within the other coroutines.

View PostJetpack_Maniac, on 13 July 2017 - 02:51 AM, said:

The OS dropping all timers made that not possible to implement as is though.

I want to stress that the timers do expire correctly and their events do go into the end of the event queue correctly - but you then cause those timer events to be removed from the queue by calling a function that removes them.

Granted, you might not expect certain functions to do that (and good luck finding any documentation telling you which ones do!), but again: you're looking for pauses. You've probably noticed getStackInSlot isn't all that fast, for example. Most functions that "interact with the world" are yielding in the same way as sleep() does - they initiate an action that will trigger an event when completed, and then they simply pull and discard events from the front of the queue over and over until they find the one that signals their job to be done.

As a general rule of thumb you can assume pretty much every function from the OpenPeripherals mod will do this.

#6 KingofGamesYami

  • Members
  • 2,708 posts
  • LocationUnited States of America

Posted 13 July 2017 - 02:56 PM

I can prove that simply blocking the computer's execution will not drop the events.

Try this code:
local id = os.startTimer(0.05) --# set a timer to go of in 1/20th of a second.
print( "Started timer " .. id )
local startTime = os.time()
while os.time() - startTime < 0.1 do end --# purposely block for 1/10th of a second.
while true do
  local e, i = os.pullEvent( "timer" )
  print( "Found timer " i )
  if i == id then
    break
  end
end


#7 Jetpack_Maniac

  • Members
  • 5 posts

Posted 13 July 2017 - 04:02 PM

View PostBomb Bloke, on 13 July 2017 - 01:34 PM, said:

The idea is that every coroutine parallel manages for you gets passed a copy of every event your main script is getting resumed with (bearing in mind that that's running inside a coroutine too - in ComputerCraft, all your code is). The code within each coroutine can pull and discard as many events as it likes without affecting the code within the other coroutines.

Alright, I've got a much better understanding of what you mean now. The state of one chunk is being affected by another chunk. I'm going to investigate the issue further.

Thanks to all who contributed to the discussion. Though I'm actually disturbed this was moved from bugs to ask a pro despite being a known issue with ComputerCraft.

Edited by Jetpack_Maniac, 14 July 2017 - 08:42 PM.






1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users