Threads causing crash?

Posted by Reno on Thu 07 Dec 2006 01:13 AM — 17 posts, 72,406 views.

#0
I've been working on a script for one of the Iron Realms games for about a day now, and I think I've gotten it really well. Except it doesn't work.

I've based it all on three silent threads running in the background at all times, checking for changes in the status of my character.

class HerbCheck(threading.Thread):
def run(self):
while 1:
while scriptenabled:
time.sleep(0.2)
cureHerb()
time.sleep(0.2)

The above is the mold from which they all come, and they all look the same, with differences in name. And before you ask; no, they do not use the same function.

The problem doesn't seem to be the threads themselves; they seem to start up fine when I tell them to. The problem, however, is that I don't know if they do. The real problem made itself known when I tried to verify that they were alive, by adding:

class HerbCheck(threading.Thread):
def run(self):
while 1:
while scriptenabled:
time.sleep(0.2)
world.note ("yay")
cureHerb()
time.sleep(0.2)

However, the moment I did this and fired the threads, MUSH crashed. No error message, just a regular old CtD.

Have I made a mistake in the code, or is this a MUSH issue?


Oh, and by the way, Nick; could you please make it so that MUSH bypasses "# -*- coding: cp1252 -*-"? It's so annoying to have to open the file in notepad every time I make a change, just to be able to get rid of the line so that MUSH recognizes/accepts the script.
Australia Forum Administrator #1
As a general rule it is not a good idea to build pauses into scripts. There are ways of managing delays built into the client, such as the DoAfter function.

Quote:

... could you please make it so that MUSH bypasses "# -*- coding: cp1252 -*-"?


I'm not sure what you are referring to here - I simply pass the script file contents to the script engine, whatever the language is.
#2
I'll definitely try the DoAfter thing, as soon as I have time. But regarding the # -*- coding: cp1252 -*-, it refers to some kind of encoding. I believe cp1252 has something to do with Windows. I think that the line, that has to be in the beginning of a Python program, tells the interpreter which encoding is used, but it seems like MUSH doesn't recognize it, or somesuch. I'm sure that if you google it, you'll get a much better explanation for it, though.
USA #3
That kind of line is a "mode line", that gives the interpreter (or whatever) extra information that isn't necessarily obvious from looking at the file.

It's possible that MUSHclient is trying to interpret that line, or something, or that it isn't recognizing that # is a comment. But then again I don't see why it would be inspecting the file in the first place, instead of just passing it over to the Python interpreter.
Australia Forum Administrator #4
Quote:

It's possible that MUSHclient is trying to interpret that line, or something, or that it isn't recognizing that # is a comment.


MUSHclient does no preprocessing on any script file. The file is passed directly to the script engine.
USA #5
That's what I thought. Reno, maybe you could give us more precise information on how you are entering this text, where, and what error message you're getting?
#6
Oh, well, when you use IDLE, you sometimes (I don't know when or why) get this "# -*- coding: cp1252 -*-" line, which is, as stated, an identifier for the interpreter. This is automatic for IDLE, and there is no way, that I have found, to disable it. If I try to load the script with the "# -*- coding: cp1252 -*-" line in MUSH, it simply does not process it. No error message, no nothing. It simply says, if I bring up the script menu again, "(not active)" after the scripting language. If I remove the "# -*- coding: cp1252 -*-" line, it works flawlessly. I'd write more, but I'm in a rush. I'll reply once I get back.
#7
There we go. Now, as to the thread pause thing, I realized that DoAfter is not what I want. The whole point of my threads were to minimize the usage of MUSH's own functions and keep as much as possible in the script, for compatibility. Is there another way of calling cureHerb() in a given interval than the one I used?
Amended on Thu 07 Dec 2006 08:13 PM by Reno
USA #8
I think Nick will have to answer the question about the comment line. I don't really know what is going on with that.

As for your delay problem, any delay done in Python will probably be a bad idea. If you delay your script, you'll be delaying the interpreter, which means you'll be delaying MUSHclient.

One thing you could *maybe* do would be to launch a new thread that delayed itself before calling cureHerb. However, depending on how the Python interpreter is implemented, that also could block MUSHclient.

What compatibility are you trying to maintain?
#9
I'm trying to make it so that I can convert it to another client as easily as possible. Not that I'd want to, since MUSH rocks, but simply for the possibility that someone else wants my system. And as to the delay thing, does delaying a thread actually delay the whole interpreter?
USA #10
Well, in that case the common thing to do is to define a gateway function, e.g. PauseMyMudScript. This function is just a direct wrapper around whatever function the client provides for delays. Then, whenever you change environments (i.e. clients) you can just change one line of code (the PauseMyMudScript function) instead of lots of things all over the place.

As for the threads, it depends on how they're implemented. If the interpreter is waiting for a thread to finish and sits around blocking until it does, and the client waits for the interpreter to finish, then yes it'll block the whole thing. I'd suggest just trying it out and seeing what you get. Still, I think it'd be a lot easier to go with the above solution than to start using threads.
#11
Hmm... I've looked over DoAfter, and considered the possibility of using MUSH's built-in timers, and come to the conclusion that even though using MUSH's timers would theoretically be possible, I don't like the thought of another system based on MUSH-specifics. Even more so, MUSH world specifics. I've written the whole script with this sole thing in mind. If anyone would like to look at the script and perhaps offer a solution to the thread problem, that would be extremely appreciated. It's just 10k or something, so it's not that much. If you feel like it, you can message me on MSN Messenger (yes, I use it), user oscar.bergqvist@bygdanet.nu or just email me at oscar@bygdea.se.

-------

Edit: After all, I did take the easy way out and modified my script to fall in line with the usage of DoAfter. Don't blame me for trying to be lazy and general at the same time :/
Amended on Thu 07 Dec 2006 10:45 PM by Reno
USA #12
Your last post is fairly confusing to me. I don't understand what you mean at all. :-)

However, I think that being lazy and general at the same time is almost a contradiction in terms, and that what you want might be impossible.

Keep in mind that whatever thread solution you use will be MUCH more complicated than using the timers. It might be general, sort of, sure, but one wonders if the huge cost is really worth it.
Australia Forum Administrator #13
I really don't think you can successfully "pause" a script. I hate to think, for example, what would happen if you had some sort of paused thread, and then reloaded the script engine. What happens to this, now dangling, thread?

As for that "first line" problem, I don't know what is happening. The behaviour of script engine DLLs is outside my control. There was one, a while ago (PHP I think), that if it got a syntax error, simply exited the client, so any script errors terminated the application totally. If Python chooses not to report an error, but doesn't initialise the script - for some reason - that is also outside my control.

I have presented various ways of implementing "pseudo-pauses" in scripts using Lua coroutines. These are quite neat, and I think you will find that Lua will be more reliable and easier to use, in the end, than other script engines.
#14
*blink* Lua...Coroutines.

There is a scripting language that supports Co-routines and I am not using it. What's wrong with me?
Australia Forum Administrator #15
See: http://mushclient.com/forum/?id=4956
#16
Martijn, the reference implementation of Python (as of 2.5) has coroutines (as an extension of generators). However the nature of generators in Python means that you can't use the exact structure Nick has for lua.

Lua's yield apparently yields all the way out to the encompassing coroutine.resume() call, whereas Python's yield only yields to the containing block, while the function call containing the yield "returns" a generator object encapsulating the function's current execution state.

So the support code looks like:
coroutine_l = {}
coroutine_i = 0

# wraps is a helper-decorator for writing decorators
# it updates __name__, __dict__ and __doc__ as appropriate
from functools import wraps

# decorator for coroutine magic, taken from PEP 342
def wait_coroutine(f):
    @wraps(f)
    def wrapper(*args, **kw):
        global coroutine_i, coroutine_l
        id = "coroutine_%s_%08x" % (f.__name__, coroutine_i)
        coroutine_i += 1
        g = f(*args, **kw)
        coroutine_l[id] = g
        coroutine_resume(id)
        return g
    return wrapper


def coroutine_resume(id):
    global coroutine_i, coroutine_l
    assert coroutine_l.has_key(id), "Could not find '%s' in list of coroutines." % id
    g = coroutine_l[id]
    del coroutine_l[id] # remove the old id from the list
    # the generator will get a new id if it hasn't stopped
    try:
        s = g.next()
    except StopIteration: # generator has stopped execution
        pass
    else: # lodge new oneshot timer
        id = "%s%08x" % (id[:-8], coroutine_i)
        coroutine_i += 1
        coroutine_l[id] = g # give the generator a new id
        # this is necessary as we are currently in the
        # execution of the old timer still so we can't
        # reuse the timer name :)
        s = int(s)
        ss = s % 60
        mm = (s / 60) % 60
        hh = s / 3600
        f = world.AddTimer(id, hh, mm, ss, '',
            eEnabled | eOneShot | eReplace | eTemporary,
            'coroutine_resume')
        assert f == eOK, "Could not AddTimer in coroutine_resume(): %s, %d" % (id, f)


And our actual "coroutine":
@wait_coroutine
def my_alias(name, line, wildcards):
    world.Note("prepare heal")
    yield 1 # wait for 1 sec
    world.Note("cast heal")
    yield 2
    world.Note("eat bread")
    yield 3
    world.Note("Done")


Stackless Python ([url]http://www.stackless.com/[/url]) has even more coroutine-y goodness :)