Cmd-- 1.11

Pages: 123456
I thought the OS kills the program if it uses too much memory.
closed account (S6k9GNh0)
No, the program kills itself from my understanding. It can be prevented. Though, it's normally not useful.
It depends on OS implementation. Bugs can occur in exceptional circumstances where the OS allows a process to write to a restricted address space in user land, which could potentially affect other processes. A process can make a system call for additional memory if needed, however.
Not necessarily. If a program tries to allocate more heap memory than the OS will give it, then the OS will simply refuse to give it any more memory. In such a case, malloc returns NULL (C) or new throws std::bad_alloc (C++). You can handle it by freeing up whatever memory you don't need and then trying again.

Rough example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
typedef std::uint64_t resource_id;

resource_id generate_resource_id()
{
    static std::uint64_t resource_id(0);
    if (resource_id + 1 == UINT64_MAX)
        throw std::runtime_error("How the hell did you generate so many "
                                 "(9x10^18 (9 quintillion (a lot))) resources? "
                                 "Even if you generated 1 billion per second "
                                 "that would take you like 300 years. Wtf.");
    return ++resource_id;
}

class cache {
public:
    static resource_id store(resource& res)
    {
        // We should probably check if the resource is already stored here.
        auto id = generate_resource_id();
        objects[id] = std::make_pair(res, std::time(nullptr));
        return id;
    }

    static resource& fetch(resource_id id)
    {
        // std::map::at throws std::out_of_range on the next line if the
        // resource doesn't exist. The caller should take that as a cue to load
        // the resource from scratch.
        std::pair<resource, std::time_t>& res = objects.at(index);
        res.second = std::time(nullptr);
        return res.first;
    }

   static void remove(resource_id id)
    {
        auto it = objects.find(id)
        if (it < objects.end())
            objects.erase(it);
        // Don't report an error if the resource isn't loaded (e.g. if it was
        // already removed) because it doesn't really matter. We could do
        // something with NDEBUG so that the programmer gets warned when
        // debugging is enabled, though.
    }

    static bool shrink()
    {
        // If the cache is already empty, then we return false to indicate that
        // we can't free up any more memory.
        if (objects.empty())
            return false;
        auto oldest = objects.begin();
        for (auto it = objects.begin() + 1; it < objects.end(); ++i) {
            if (it->second.second < oldest->second.second)
                oldest = it;
        }
        remove(it->first);
        // Return true to indicate that we managed to free some memory by
        // shrinking the cache.
        return true;
    }
private:
    static std::map<resource_id, std::pair<resource, std::time_t>> objects;
};

void allocate_buffer(char*& buffer, std::size_t size)
{
    while (true) {
        try {
            // Try to allocate the buffer.
            buffer = new char[size];
        } catch (const std::bad_alloc& alloc) {
            // Uh-oh! We're probably out of memory. Try to shrink the cache. If
            // that doesn't work, then we have big problems, so we pass the
            // error back to the caller. If we were really clever and didn't
            // care about performance we could try to do some cool CSI analysis
            // of the error message and figure out exactly what was wrong.
            if (!(cache::shrink()))
                throw alloc;
        }
        // If we got here, then it means we successfully allocated the buffer.
        return;
    }
}

void function_which_wants_to_allocate_a_buffer()
{
    char* buffer;
    std::size_t size = 32768;
    try {
        allocate_buffer(buffer, size);
    } catch (const std::bad_alloc& alloc) {
        // Lazily pass the error back to the caller because this function isn't
        // as hardworking as allocate_buffer, and gives up after its first try.
        // This is for the best, though, because otherwise we'd be here forever
        // waiting for whatever the problem is to magically resolve itself. We
        // could use a set number of tries, but that's probably a waste because
        // if shrinking the cache didn't fix the problem then the problem is
        // probably more serious.
        throw std::runtime_error(alloc.what());
    }
}
Last edited on
Of course, I agree that the OS will refuse to allocate more memory for a specific process in some cases, though I was referring to system calls. new[] and delete[] are user land routines (for most OS implementations), not system calls. *EDIT
Last edited on
@Montario79
That post was intended as a reply to Lumpkin, not to criticise/disagree with your answer. I'm sorry I didn't make that clear.

new[] and delete[] are user land routines (for most OS implementations), not system calls if I am correct.

That's true, but where do you think new gets the memory from? Whether it calls the OS every time or uses a pre-allocated pool or uses some other approach, ultimately it gets the memory from the OS. Most implementations probably don't go to the OS for every memory request. They probably allocate memory in blocks; but if you ask for more memory than is currently allocated, they would have to go to the OS for more. If the OS can't allocate any more, that's the most likely case that new would throw std::bad_alloc. I can't see why an implementation would ever throw std::bad_alloc if it already owned the amount of memory being allocated, as would be the case if it wasn't asking the OS. There could be heap corruption, I guess, but if there is, it's almost certainly due to programmer error (and in that case, handling std::bad_alloc won't help at all).

@compterquip
My previous post shows a scenario that's probably reasonably common where it would be useful to handle the error where the OS won't allocate any more heap memory.
@chrisname Oh, I'm sorry! :*. Though in response to your latest reply I see where you are coming from.

but if you ask for more memory than is currently allocated, they would have to go to the OS for more.


That was the point I was trying to put across.
closed account (S6k9GNh0)
chrisname, of course. Although, searching on Sourceforge, I bet you couldn't find more than maybe a handful of projects that do something of that sort. The idea is that if there is no memory, let the program die. But it depends on what it's normal environment is among other things.

Either way, I know it can be useful but it's often not.
Last edited on
@compterquip
It's totally dependent on the project. There's two broad categories of programs: ones that are designed to do all of their computations in one go and then exit, and ones that need to run continuously for an indefinite period of time. Most programs fall into the former category, but in the latter one you have software like servers, operating systems, finance systems, etc. A server can't afford to simply die when the OS says there's no memory to create new threads with; it logs the error, refuses new connections until the situation is resolved, and carries on with all the requests it's already handling. If one of the existing threads can't get any more memory, then just that connection gets dropped. The entire server doesn't crash. That sort of thing is the purpose of exception handlers that try to do some kind of error recovery. My example was just that - an example (and a fairly specific one). The general concept of automatically recovering exception handlers is more widespread than what you suggested - "a handful of projects". Although maybe you were referring just to memory allocation, which probably is something of an "edge case".
Last edited on
¿is there a way to simply block the process until there is enough memory available?
closed account (S6k9GNh0)
Actually, some people suggest ( I don't recall the article ) that if a server runs out of memory, just restart the server. Let it die and let the startup script resolve it. It doesn't always make sense to be able to shrink the amount of memory used especially when all of that memory might be vital to a program (which is often the case). You might have better luck trying to compress data and decompress it whenever needed.
Last edited on
@ne555
Why would you want to block the entire process? I guess you could idle and wait:
1
2
3
4
5
6
7
8
9
10
11
void allocate_buffer(char*& buffer, std::size_t size)
{
    while (true) {
        try {
            // Try to allocate the buffer.
            buffer = new char[size];
        } catch (const std::bad_alloc& alloc) {
            std::this_thread::yield(); // Try again later.
        }
    }
}

or even busywait, but I really wouldn't recommend either option.

You could also have a timer that wakes the program up if it's been idling for too long.

computerquip wrote:
if a server runs out of memory, just restart the server. Let it die and let the startup script resolve it.

But that drops all of the connections. My way only drops connections that can't be handled, which keeps the server operating.

It doesn't always make sense to be able to shrink the amount of memory used especially when all of that memory might be vital to a program (which is often the case)

My example was of a program that uses a cache as an optimisation so it doesn't have to load resources from e.g. the disk as frequently. If the cache is using too much memory then it falls back to loading from disk.
closed account (S6k9GNh0)
chrisname, it probably can't serve anything either since that would probably require memory allocation. You're method is rather unrealistic I would think...
Last edited on
computerquip wrote:
chrisname, it probably can't serve anything either since that would probably require memory allocation. You're method is rather unrealistic I would think...

It requires (heap) memory to create new connections (because they run in a new thread and threads need additional memory) and to load resources to transfer them over connections. With my way, the server refuses new connections, and drops connections that try to allocate more memory than is available. In that way, it hangs on to enough memory to handle the majority of the connections it already has.
> Why would you want to block the entire process?
the process needs a resource, it does not obtain it, so it has nothing to do.

simply terminating the process is quite a waste
saving its state could take a really long time. Maybe long enough that other process release some memory.
so let's wait.


PS: I don't like using exceptions as error codes.
1
2
while( not (buffer=new(std::nothrow)char[size]) )
   std::this_thread::yield();
Last edited on
closed account (S6k9GNh0)
chrisname, I was assuming that a new connection did not use a new thread. A connection per thread is also horrible scaling. Also, you're method is still not desirable. In what reality would you want a server dropping connections and refusing new connections?
@cire, sorry if my reply is late, your post somehow inserted itself into an earlier part of the thread, didn't notice it until now. Sorry about the state of the project, I will just include the source files next time I update the project, no reason for anything else to be included.

I thought I had pruned the useless stuff out of the code, my apologies, CFunction newFx0, Check_vector_by_id_string, and Return_vector_element_pointer_by_id_string should not be in there, my apologies for including those (facepalm). They're functions that I was experimenting with before release, but were not ready to say hello world just yet.

@Chrisname, thanks for the great responses, I can definitely see the use of the wrapper for pointers, having it clean up once out of scope would be invaluable.
Holy crap, I didn't mean to derail this thread, sorry.
@computerquip
You're probably right.

ne555 wrote:
the process needs a resource, it does not obtain it, so it has nothing to do.

simply terminating the process is quite a waste
saving its state could take a really long time. Maybe long enough that other process release some memory.
so let's wait.

That's true.

PS: I don't like using exceptions as error codes.

Personally I find them superior to return values, although I understand there is (probably very small) a performance hit. I prefer exceptions for a few reasons:
* they don't require all your functions that may fail to return a type that can indicate failure (like bool, int or T*)
* functions which return references can indicate failure if you use exceptions, but not if you only use return values (unless you use output parameters, but I don't like to do that; I think parameters should be for defining the function's behaviour and return values for describing the result)
* with return values, if a calling function doesn't want to handle errors (e.g. if it wants to just die when something bad happens) it still needs error checking code:
1
2
3
4
5
6
7
8
void caller()
{
    if (callee() < 0) {
        // Manually commit seppuku when callee fails.
        std::cerr << "Error occurred\n";
        std::terminate();
    }
}

whereas, with exceptions,
1
2
3
4
5
void caller()
{
    callee();
    // Automatically dies if callee throws an exception.
}

* exceptions are more consistent because an exception always indicates failure† whereas the meaning of a return value differs between different functions and APIs, e.g. some functions return -1 to indicate failure, some functions return false, some return NULL (or nullptr), some may use a different positive integer for each error case -- consistency is not as enforced as it is with exceptions
* http://www.parashift.com/c++-faq/exceptions-eliminate-ifs.html
* http://www.parashift.com/c++-faq/exceptions-avoid-two-return-types.html
I almost exclusively use return values for when I'm trying to get a value (which is what they're for). Your code example would be a situation where I might use one to indicate failure because that loop would be more complex otherwise, but generally speaking, I don't use them for that in C++.

† unless you were doing something really weird, like
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void thrower()
{
    std::string line;
    std::cout << "What is your name? ";
    std::getline(std::cin, line);
    throw line;
}

void catcher()
{
    try {
        thrower();
    } catch (const std::string& name) {
        std::cout << "Hello, " << name << '\n';
    }
}

but, knowing that it's called an exception, and knowing what the word "exception" means, you would have to be using it wrong intentionally. There's no way someone who knows what "exception" means could do it by accident. That said, I think maybe C++ should make sure only types derived from std::exception are throwable.
Last edited on
exceptions are more consistent because an exception always indicates failure
Actually, in very specific circumstances, exceptions can be extremely useful to control flow.
In my implementation of coroutines I wanted to give code a chance to call destructors when a coroutine object is destructed while the coroutine was in the middle of a call, so from the destructor I signaled the coroutine to unwind the stack back to the coroutine entry point.
Pages: 123456