P49: Memory allocation proved impossible in Counterfeit Monkey

It seems to me that this crash is not really related to the “more than 60 objects in the room” error message, but is in fact run-time problem P49: Memory allocation proved impossible.

I wrote a test command that moves every item in the game to the current room, and that causes run-time error P49.

I don’t know where to even start debugging this. It sounds like a failed malloc(), and that seems unlikely to happen on a modern desktop machine unless there is an infinite loop somewhere.

2 Likes

It looks like that RTP is issued by the Flex system, which provides a memory heap with a standardized interface (over a fixed block of memory on Z-machine and malloc on Glulx). When it crashes, it should also (hopefully) print a couple words to the screen before the RTP happens, signalling what’s wrong; does anything appear?

1 Like

I’m not sure which screen that would be. The Inform IDE only prints a generic message about dynamic memory allocation. Glulxe only prints *** Run-time problem P49: Memory allocation proved impossible to the game window, and nothing to the console.

It should be the main interpreter window, but sometimes when an RTP happens, printing gets tricky.

Unfortunately, the Flex system is one part of I7 I know basically nothing about. I’ve never needed to delve into it before. If it’s getting overwhelmed, that means too many “block values” (values that don’t fit into a 32-bit word—lists, stored actions, indexed texts, that sort of thing) have been created at one time, or they’ve gotten too large. The standard library basically never uses these sorts of things, so it’s most likely somewhere in the Counterfeit Monkey code.

My guess is that, somewhere in the code for looking, it takes “the list of visible things” and manipulates it. When there are too many things in that list, the heap overflows. If it can be reproduced reliably, can you execute a RULES ON right before it? That will hopefully pinpoint where exactly this list is being constructed. I did a search for "the list of" in the source and didn’t come up with anything relevant.

It prints a huge number of [Rule “scene description text rule” applies.] before the crash, so it seems likely to be an infinite loop of some kind.

Hmm, that’s very curious.

A scene has a text called description.
When a scene (called the event) begins (this is the scene description text rule):
    if the description of the event is not "",
        say "[description of the event][paragraph break]".

So something’s getting messed up with the scene starting machinery. I have no idea what that could be, though. CM doesn’t seem to use scenes very extensively.

I’m worried that this particular error might be caused by my test action, which moves a lot of things to the location which can never be present at the same time in the actual game, and that it might not be the same error as the one in the bug report.

Ah, that could be it. There might be scenes getting triggered by the particular objects you’re moving, and those scenes are interfering with each other’s conditions in some way that doesn’t happen in actual gameplay.

Take a look at the scene beginning conditions, see if any involve the presence of particular objects, and avoid using those ones in the test case?

I’ve tried, but no cigar so far. The code I’ve added is really messy, but here it is in case anyone wants to have a go. I broke the conditions up into three lines just to make it more readable, but it still ended up very unreadable.

You may have to remove one of the existing test actions in Tests.i7x in order to make it compile.

Understand "toomany" as room-filling. Room-filling is an action applying to nothing.

Carry out room-filling:
	repeat with item running through things which are not facts:
		unless item is a quip or item is a memory or item is a person or item is software or item is the tick:
			if item is portable and item is not heavy and item is not scenery and item is not in repository and item is not a g-window and item is not a subject and item is not quip-repository and item is not backup-repository and item is not current-paragraph and item is not a backdrop and item is not part of something and item is not herds:
				say "Moving [item] to current location.";
				move item to location.

Running out of stack should be reported distinctly to being unable to use malloc. That should be reported as a bug.

1 Like

This is all in regard to10.1, right?

Memory allocation proved impossible is RTP_HEAPERROR. If it’s produced by a routine from Flex.i6t, the invocation is wrapped by FlexError which precedes it with a contextual message:

*** Memory [...] ***

where [...] is one of:

  • ran out
  • too fragmented
  • for head block not available
  • for tail block not available
  • failed resizing null block
  • failed resizing inextensible block

If can also come from BlockValues.i6t, where it’s usually preceded by:

*** Value handling failed: [...] ***

courtesy of the BlkValueError wrapper (there’s a longer list of possibilities for [...]I won’t detail). You didn’t mention either message; if that’s because you’re not seeing them, that leaves the one place something issues RTP_HEAPERROR directly without either of those wrappers, which is this, in BlockValue.i6t:

[ StackFrameCreate size new;                                                              |                RTP_Buffer-->2 = par2;
        new = I7SFRAME - WORDSIZE*size;                                                   |                RTP_Buffer-->3 = par3;
        if (new < blockv_stack) { RunTimeProblem(RTP_HEAPERROR); @quit; }                 |                RTP_Buffer-->4 = ln;
        I7SFRAME = new;                                                                   |                RTP_Buffer-->5 = file;
];  

That should probably use RTP_MSTACKMEMORY, per @Dannii above.

4 Likes

Counterfeit Monkey is still 6M62 (9.3), so I’m not sure how much of the above still applies.

FYI, there was an issue affecting memory allocation in 6M62 on Glulx in which attempts to allocate blocks would fail under pretty rare circumstances. This was due to a mismatch between the meaning of a constant and its use in a particular routine.

I don’t know if it’s the source of your problem, but details and a 6M62-compatible fix are available here.

I think this is it: the scene beginnings are recursing infinitely and exhausting the stack. I didn’t realize this could produce the heap RTP!

Still no idea how to detect the exact problem, though, except searching through all scene beginning definitions and figuring out which could trigger each other.

Or maybe it’s not recursive, and there’s a single recurring scene whose beginning and end triggers are both constantly being met? But I thought the scene-change machinery only checked each scene’s conditions once, specifically for situations like this. (“X is a recurring scene. X begins when the apple is on-stage. X ends when the pear is on-stage.” when both are on-stage.)

Makes sense though. If a rule does text manipulation (or deals with other dynamic memory types), the I6 routine is wrapped in another routine which allocates some pseudo-stack-space, calls the inner routine, and then releases the space.

This is because Glulx can’t directly put large objects on the stack – only 32-bit words. (I think Graham curses my name and ancestors about this every morning.) So I7 maintains a pseudo-stack on the heap to permit the existence of temporary objects.

2 Likes

It seems to have been caused by the boar mating scene. Commenting out the begin condition (Boar mating begins when the boar is enclosed by location and the suid is enclosed by location) removes the memory allocation error.

It made sense once I realised that I was moving the repository object, which contains all uncreated things, to the current location.

Still has nothing to do with the bug report, though.

1 Like

A nuance for those who care about such details: At least in 6M62, scene changes are prevented from recursing indefinitely. A scene recursion problem would generally produce a message >--> The scene change machinery is stuck., courtesy of routine DetectSceneChange(), after about 20 changes taking place during the same scene change check.

It’s worth considering that a very large game (written in Short’s style) could exhaust the block value stack, but the default value of that is 2048, which can handle quite a few frames (they’re generally small). WWI 20.2 Memory limitations describes how to adjust the dynamic memory allocation constant, which describes the number of bytes being requested for the block value stack. Increasing it to 16K as in the example there would be expected to solve a typical stack exhaustion problem.

1 Like