This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
grabie2's profile picture

[BUG] [temp solution] multitasking OS, strange environments

Started by grabie2, 28 December 2012 - 02:16 PM
grabie2 #1
Posted 28 December 2012 - 03:16 PM
I have temporary solution, if you want see last post. Problem showed in this thread is definitely bug and will be reported

For moderators: please close this thread

Hello everyone!

I went into strange situation with tables. The files that may be involved in this problem: bin/netOS bin/ttyManager, however other files in archive are needed to boot OS. It has to be on disk, or you have to change path in boot file.

When you boot netOS, after few startup messages it goes to screen like this:

netOS indev
TTY 1, PID 2
username1>
that is changing with this in period of 5 sec:

netOS indev
TTY 1, PID 3
username0>

the number after TTY is "term.id"(I use custom APIs) which is defined here:
bin/ttyManager

local function newTTY(stdin, stdout, _f, _api)  --line 43
  _G = getfenv()
  local self = {}
  self.active = false
  local ttyStdin = task.nullin
  local ttyStdout = {}
  ttyStdout._x = 1
  ttyStdout._y = 1
  ttyStdout.id = #ttys --HERE line 51
  ttyStdout._textColor = colors.white
  ttyStdout._backColor = colors.black
  ttyStdout._blink = true
  ttyStdout._width, ttyStdout._height = stdout.getSize()
  ttyStdout._buf = {} --line 56

....

  local process = task.new("tty"..tostring((#ttys)+1), TTY, _api, ttyStdin, ttyStdout, (#ttys)+1, _f) --line 310
  self.activate = function()
	self.active = true
	ttyStdout._repaint()
process.setStdout(ttyStdout)
	process.setStdin(stdin)
	stdout.setBackgroundColor(ttyStdout._backColor)
stdout.setTextColor(ttyStdout._textColor)
  end
  self.deactivate = function()
	self.active = false
	process.setStdin(ttyStdin)
process.setStdout(ttyStdout)
  end
  table.insert(ttys, self) --HERE this increments this number
  return self
end --line 326



"stringed" here:


local function TTY(n, _f, PID) --line 31
  local fnFile, err = loadfile( fs.combine(os.getPath(), "/bin/lua" ))
  if fnFile then
	--_G = getfenv() -- workaround of CC bug
	local _api = _G
	local tEnv = {}
	setmetatable( tEnv, { __index = _api } )
	setfenv( fnFile, tEnv )
  end
  login(os.version().."\n TTY "..tostring(term.id)..", PID="..tostring(PID), fnFile, PID) --HERE - passed as parameter to function
end --line 31

number after username("username#>") is THE SAME number, but displayed a bit later :


local function login(welcome, _f, PID) --line 8
  os.addEventListener("key") -- #1
  os.addEventListener("char") --#1
  local _exit = false
  while not _exit do
	term.clear()
	term.setCursorPos(1, 1)
	term.print(welcome)
--	for n = 1, 3, 1 do
	  term.write("username"..tostring(term.id).."> ") --HERE
	  local username = term.read()
	  term.write("password"..tostring(term.id).."> ")
	  local password = term.read()
	  term.print("")
	  if os.login(username, password) then _exit = true break end
--	end
  end --line 29

  os.removeEventListener("key")
  os.removeEventListener("char")
  _f(PID)
end

#1 os.addEventListener(event) makes task listen to given event, also yields coroutine, so other can run

So these numbers should be the same, right?

more tests:
bin/ttyManager

function run(PID) -- #1 line 329

  newTTY(task.stdin, task.stdout, login) --#2
  newTTY(task.stdin, task.stdout, login) --#2
  
  os.addEventListener("timer")

  ttys[2].activate()
  local test = true
  local timer  = os.startTimer(0) --#3
  while true do
	local e, p1 = os.pullEvent()
if e == "timer" and p1 == timer then
   if test then
	 ttys[2].deactivate()
  ttys[1].activate()
  test = false
   else
	 ttys[1].deactivate()
	 ttys[2].activate()
  test = true
   end
   timer  = os.startTimer(5)
end
  end

end --line 356


#1 function run is ran bu OS as task
#2 task.stdin can be found in bin/netos line about 350 it basicly redirects user input events THIS WORKS
task.stdout can be found also in bin/net os about the same lines as above, basicly termAPI, but a little improved to os needs
#3 puts event immediately to queue, so (as of my calculations) the loop will loop right after "os.addEventListener("char")" of tty processes, so before rendering stuff, BUT
if you set it to for example 1 sec delay, so it will run after some rendering in login function(tty process)
what happens? … task with lower PID(the first created one) doesn't show anything! if you turn of

	term.clear()
	term.setCursorPos(1, 1)
in login function you can see, that instead of drawing in it's own console first tty draws on second's. BUT if you press ENTER on "empty" screen you can see that now it draws properly, because we changed "stdout"(termAPI) of this task in "activate" function, BUT we changed to to THE SAME as during creation, why is that?
They shouldn't be interfering with ANY other task In dev version of OS I have about 15 different tasks ran from different sources and nothing like that happens, although stdout mechanism wasn't tested much, so can be buggy.

Please help me if you followed along, thanks for your help!

I hope you understand what i said ;)/>
[attachment=829:netOS.zip]
Grim Reaper #2
Posted 28 December 2012 - 05:28 PM
I don't think I quite understand what you're asking here. However, you might want to consider (although I'm not entirely sure how relevant this might be) that tables are passed by reference and thus to actually use a copy of a table you'd have to write your own table copying routine.

Again, I don't understand what your problem is and would like to help, but a little more clarification (and maybe a summary) as to what the problem is would help us.

Sorry for not replying with anything (maybe) useful.
grabie2 #3
Posted 28 December 2012 - 11:34 PM
Thanks for reply!

I though that tables are passed by reference,but problem is, that I have function which creates : "local ttyStdout = {}" adds some fields and functions to that, and passes that table to: "local process = task.new("tty"..tostring((#ttys)+1), TTY, _api, ttyStdin, ttyStdout, (#ttys)+1, _f) ".
When I call this function once, everything is OK, but when I call this function second time it somehow overrides the table that the first call created and send.
BUT through some magic when I override this variable in environment of this(created by task.new) coroutine(Only when overrode like in function "run") it changes to proper table.
Could someone go through my code and find why? Look what happens when you boot up OS and what could be expected.

But, I'll try making copy of this table maybe somewhere I miss some reference or we have bug in CC lua that makes it happen

EDIT: No, reference isn't the case.
Keep in mind, that there is field ID, which shows which table is used: then we fist run coroutines it's in all routines the same - "1", which is second table, but after override it by calling "activate" it changes in one routine to "0" - proper one. If you can please look at computer screen and code, maybe you'll find something that I missed.
Edited on 28 December 2012 - 10:53 PM
grabie2 #4
Posted 29 December 2012 - 02:09 AM
ok, something really strange happens, I made it print it's id and number, before creating task, and inside task:

  print("tty "..tostring((#ttys)+1).." id:"..tostring(ttyStdout.id))
  local process = task.new("tty"..tostring((#ttys)+1), TTY, _api, ttyStdin, ttyStdout, (#ttys)+1, _f)
  self.activate = function()
    self.active = true
    --ttyStdout._repaint()
process.setStdout(ttyStdout)
    process.setStdin(stdin)
    stdout.setBackgroundColor(ttyStdout._backColor)
stdout.setTextColor(ttyStdout._textColor)
  end

and


local function TTY(n, _f, PID)
  local fnFile, err = loadfile( fs.combine(os.getPath(), "/bin/lua" ))
  if fnFile then
    _G = getfenv() -- workaround of CC bug
    local _api = _G
    local tEnv = {}
    setmetatable( tEnv, { __index = _api } )
    setfenv( fnFile, tEnv )
  end
  print("TTY "..tostring(n).." here, my id="..tostring(term.id)..", PID="..tostring(PID))
  --login(os.version().."\n TTY "..tostring(term.id)..", PID="..tostring(PID), fnFile, PID)
end

and what happens?

you can see that they are swapped !
why? If we turn of: "ttys[1].activate()" in run function

you can see, that they are the same! (this also happens if I "activate" second TTY)

For testing purposes I changed few thins in bin/ttyManager, first commented few things:
- repeainting screen on TTY activate
- running login function in TTY function
Also turn instant printing to screen for tty, by setting "ttyStdout.active" true
Changed ttyManager in attachment

Maybe problem is that all TTYs are running the same function, but in different environment(should be)?
[attachment=830:ttyManager.zip]
CoolisTheName007 #5
Posted 29 December 2012 - 03:58 AM
Ok some ideas: try using OO (i.e. a tty class) to not have to redefine each TTY function each time a new TTY is created. Also
print("tty "..tostring((#ttys)+1).." id:"..tostring(ttyStdout.id))

ttyStdout.id is being set to #ttys, not #ttys+1.
grabie2 #6
Posted 29 December 2012 - 06:51 AM
Thanks man!

ttyStdout.id is being set to #ttys, not #ttys+1.
yea, It was created, to just see which stdout TTY get, it doesn't have any other function

try using OO (i.e. a tty class) to not have to redefine each TTY function each time a new TTY is created
What do you mean by that? Where I redefine TTY function? it's defined only once, and it's called as new task with it's own environment, but I'll try that I'll create two functions and see if they interfire with each other if not, maybe create TTY function dynamically like that:
in function newTTY:

self.TTY = function(n , _f, PID)
...
end
huh?
Lots to try, once again thanks for rescuing me :D/>

EDIT: OK, running two TTYs as different functions doesn't change anything, so bug is somewhere else.
Edited on 29 December 2012 - 06:07 AM
CoolisTheName007 #7
Posted 29 December 2012 - 07:35 AM
snip

What I mean is that stuff like:

local ttyStdout = {}
...
ttyStdout.setTextColor = function( color )
	ttyStdout._textColor = color
  if self.active then stdout.setTextColor(color) end
  end
should be

ttyStdout_meta={}
ttyStdout_meta.setTextColor = function(self, color )
	self.ttyStdout._textColor = color
  if self.active then self.stdout.setTextColor(color) end
end
local function newTTY(stdin, stdout, _f, _api)
...
local ttyStdout = setmetatable({},ttyStdout_meta)
Otherwise you are creating new functions each time you call newTTY; just because they are inside another function, doesn't mean that there isn't a dynamic evaluation. I'm not sure myself; plus, I mostly read Lua documentation, not LuaJ, and that may be different.
But I see your code uses process.setStdout(ttyStdout), and that may complicate stuff.
grabie2 #8
Posted 29 December 2012 - 10:52 AM
yea, but setStdout is just changing termAPI in env of task, I'll try it, maybe not today, cause I'm a little busy.

BUT Some of this values in "ttySdtout" are task specific, also some functions depend on actual "stdout" passed to newTTY. It will be harder but actual solution should be safer(in meaning of my problem) than using one table of functions.

Thanks for reply

BTW. what is "snip", I've seen it in many posts, but I can't figure it out?
CoolisTheName007 #9
Posted 29 December 2012 - 12:25 PM
I have no idea, I just followed the crowd.
grabie2 #10
Posted 29 December 2012 - 12:34 PM
metatables won't work in this case, it has to create new functions, like task.new() does. I think I have to track it down to task managing level. I hate bugs like that.

stdin and stdout are new features and you know…

WHAT THE ….?

I print stdout.id there:
bin/netOS about line 390

----adds API to task environment------------------------------------------------
  self.addAPI = function(name, api)
    local _env = getfenv(_f)
    _env[name] = api
    setfenv(_f, _env)
print("task creating: PID="..tostring(PID)..", stdout.id="..tostring(getfenv(_f).term.id))
  end
----pre initialization code-----------------------------------------------------
  pidCounter = pidCounter + 1
  if _api then setfenv(_f, _api) end
  self.addAPI("term", stdout)
 
  _tmp = nil
AND IT's printing correct value ?! AND task(TTY) prints wrong ID. WHAT THE …?
<rage>
Edited on 29 December 2012 - 11:49 AM
grabie2 #11
Posted 30 December 2012 - 03:34 AM
Has anyone idea what went wrong? I'm setting up proper table on env of the function, and function has wrong env, WHY?

edit:
I found something, If i print inside TTY function:

print("TTY "..tostring(n).." here, my id="..tostring(getfenv(0).term.id)..", PID="..tostring(PID))

so field that should NEVER exist in global env, in first TTY is nil - proper value, but in second it's … 1! HOW? Where I setup global env? Or coroutine has it's own global env(is thread in meaning of lua docs)?

EDIT: derp, I was running two different functions, that's why it printed once nil, and second time '1'

edit #2:
OK another discovery: when I try to set up global env on top of bin/netOS:

local nativesetfenv = _G["setfenv"]
_G["setfenv"] = function(...)
  print("setting env : [1]="..tostring(arg[1]))
  nativesetfenv(unpack(arg))
end
setfenv(0, _G)
It returns error: 'setfenv' cannot change environment of given object
But according to lua docs: http://www.lua.org/m...tml#pdf-setfenv
When I give instead of function 0 this should set up global environment, is it something with LuaJ ?

Another interesting thing:

It looks like I somewhere I setfenv first function with wrong API.

EDIT:
OK, now I'm pretty sure that this is somewhat CC or LuaJ bug, look at that:

in mainOS file I setUP hook to setfenv:

local nativesetfenv = _G["setfenv"]
_G["setfenv"] = function(...)
  if arg[2].term then
	print("setting env : [1]="..tostring(arg[1])..", id="..tostring(arg[2].term.id))
  else
	print("setting env : [1]="..tostring(arg[1]))
  end
  nativesetfenv(unpack(arg))
end
nativesetfenv(1, _G)
and I made TTY function dynamically created - as part of self table in newTTY function:

local function newTTY(stdin, stdout, _f, _api, _f2)
  local self = {}
  self.TTY = function(n, _f, PID)
	print("TTY "..tostring(n).." here, my id="..tostring(term.id)..", PID="..tostring(PID))
  end
And as you can see on screen it NEVER sets termAPI in first TTY to the one with id=1, and what we see? I has termAPI with id = 1. Please someone explain it to me.
My current code(bin/netOS and bin/ttyManager): [attachment=842:bin.zip]
Edited on 30 December 2012 - 08:36 AM
grabie2 #12
Posted 30 December 2012 - 12:18 PM
I found temporary solution for this. I simply pass right stdout by argument to TTY function, but definitely this is a bug, when I make this part of OS I'll report it.

For moderators: please close this thread.