Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Priority Messages #73

Merged
merged 2 commits into from
Jan 22, 2025
Merged

Conversation

rickard-green
Copy link
Contributor

@rickard-green rickard-green commented Jan 7, 2025

In some scenarios it is important to propagate certain information to a process quickly without the receiving process having to search the whole message queue which can become very inefficient if the message queue is long. This EEP introduces the concept of priority messages to the language which aim to solve this issue.

eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Outdated Show resolved Hide resolved
eeps/eep-0076.md Show resolved Hide resolved
Copy link
Contributor

@Maria-12648430 Maria-12648430 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool, I was hoping for something like this for a while ❤️

Comment on lines +53 to +54
long time until the receiver fetch this message. The situation will at this
point very likely have become even worse.
Copy link
Contributor

@Maria-12648430 Maria-12648430 Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or the situation could have resolved itself by then (like, if the messages occur in sudden bursts in a by and large relaxed scenario), at which point it would be too late to change handling strategies.

Copy link
Contributor Author

@rickard-green rickard-green Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the message comes so late that it does not reflect the current state and you may not want to act on it. I'll rewrite this.

eeps/eep-0076.md Outdated
Comment on lines 186 to 189
The receiver process can at any time disable reception of certain priority
messages by passing `false` as second argument to any of the above listed
`process_flag/2` BIF calls.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a sending process specified in a priority process flag exits, will it automatically be removed from the process flags of the receiver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the reference implementation this is currently not done, but this can be implemented.

eeps/eep-0076.md Outdated
Comment on lines 168 to 174
* *Priority Marked Messages* - A message is marked as a priority message by the
sender by passing the option `priority` in the option list that is passed as
third argument to the `erlang:send/3` BIF. The receiver opts-in for reception
of priority marked messages from a specific sender by calling the
`process_flag/2` BIF like this:
`process_flag({priority_marked_message, SenderPid}, true)`.
Copy link
Contributor

@Maria-12648430 Maria-12648430 Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, how could this be put to work for system messages originating from sys and, by extension, from proc_lib? One would probably want to prioritize those, but AFAICT the sending process is the calling or a freshly spawned process, ie it can't be known to set the SenderPid for the priority_marked_message flag in the receiving process.

Copy link
Contributor

@Maria-12648430 Maria-12648430 Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, this restriction to prioritize messages from a specific sender pid comes with a catch.

Contrived example:
Let's say a process A configures priority_marked_message for messages from another process B. Process B however starts another process C to perform some complicated task (in order to stay itself responsive meanwhile), and in the end this process C should send a priority message on behalf of process B to process A. But since process C is not configured for priority_marked_message in process A, this message (which, again, is sent on behalf of but not by process B) won't be prioritized in the mailbox of process A.

So, as a first idea, maybe priority_marked_message should accept aliases too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, how could this be put to work for system messages originating from sys and, by extension, from proc_lib?

To me it feels a bit like stretching what the priority messages was intended for

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, as a first idea, maybe priority_marked_message should accept aliases too?

Hmm, I like it. Perhaps the priority option can be replaced with priority aliases all together.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's too far fetched to expect system messages to be handled optionally as priority messages. Imagine you are debugging an overloaded process and you are trying to get the process state. Today you must be very patient. With priority system messages you can immediately debug things. If I understand correctly aliases would enable that scenario (since system messages are ultimately normal messages that could be prioritised).

Comment on lines +138 to +141
multiple priority messages would accumulate in reverse order. Having these two
sets of messages ordered internally by reception order at least to me feels the
most useful. Just as in the case of ordinary messages we will probably want to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, keeping reception order is important, imperative, the only sensible thing to do. More so since the receiving process can't distinguish prioritized from ordinary messages.


Note that the reception order of signals is not changed. If a process sends an
ordinary message and then a priority message to a another process, the ordinary
message will be received first and then the priority message will be received.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"received" here doesn't mean "returned by receive" I take it, since the whole point of this proposal is to change that order?

Copy link
Contributor

@Maria-12648430 Maria-12648430 Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly, but it leads to an interesting point.

If the order of events turns out like this...

  1. process A sends ordinary message M to process B
  2. process A sends prio message P to process B
  3. process B calls receive
  4. process B calls receive

... then process B will receive the messages in order P then M.

However, if the order of events turns out like this...

  1. process A sends ordinary message M to process B
  2. process B calls receive
  3. process A sends prio message P to process B
  4. process B calls receive

... then process B will receive the messages in order M then P.

To be clear, that is pretty much as expected. It could/should be mentioned maybe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"received" here doesn't mean "returned by receive" I take it, since the whole point of this proposal is to change that order?

Correct. The signal is received before it is inserted into the message queue. The name of the receive expression is in my opinion quite misleading. A less misleading name (although not as nice name) could have been fetch_message_from_message_queue.

eeps/eep-0076.md Outdated
The reason for not having options for accepting all priority marked messages,
all exit messages, or all monitor messages as priority messages is the risk of
introducing bugs when code in other modules are called from the process
accepting priority messages. For example, if a process enables all monitor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this point should be emphasized. Disabling the message receive order guarantees should be opt-in and based on explicit agreement between sender and receiver (which may be the same module).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess when you say "the same module" you really mean "the same process"? 😅

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean the same module. Say a gen_server where an API function (called from outside the server process) ends up passing a priority message to the server process -- the code for both sender and receiver is in the same module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, ok, after re-reading the rest of the paragraph I get that. However, the significance of the module from which a call is made is unclear to me. Why does it matter? 🤔

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only significance is that with both sender and receiver in the same module they are in a better position to agree on the validity of using priority messages, which otherwise would be totally invalid.

My point is that priority messages break long-standing Erlang semantics and are totally invalid in the general case. They must be opt-in, which requires a close relationship between sender and receiver.

eeps/eep-0076.md Outdated
in the future might be modified in a way so that it relies on the selective
receive optimization taking effect. Therefor I find it unacceptable to disable
the selective receive optimization. The priority message implementation needs
to be able to preserve the selective receive optimization.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say MUST not needs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rephrased this.

eeps/eep-0076.md Outdated
Comment on lines 168 to 171
* *Priority Marked Messages* - A message is marked as a priority message by the
sender by passing the option `priority` in the option list that is passed as
third argument to the `erlang:send/3` BIF. The receiver opts-in for reception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an !-like operator for sending messages prioritized (like, say, !!)? Probably not, having a convenient operator may lead to abuse and/or over-use. Also, it would be somewhat hard to factor hypothetical priority levels into it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to encourage the use of priority messages that much. They should be rare enough to not need an operator.

eeps/eep-0076.md Outdated
third argument to the `erlang:send/3` BIF. The receiver opts-in for reception
of priority marked messages from a specific sender by calling the
`process_flag/2` BIF like this:
`process_flag({priority_marked_message, SenderPid}, true)`.
Copy link

@potatosalad potatosalad Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to allow something like this for allowing any process on the current node to send priority messages to the receiving process?

process_flag({priority_marked_message, local}, true).

Otherwise, for cases like overload protection where it may be a system-wide decision to change strategies, it could require having a secondary process used solely for the purpose of forwarding high priority messages to the receiving process based them opting in to receive priority messages from this secondary proxy process.

Demonstration the "priority proxy" thing:

-module(eep76_local_senders).

worker_start() ->
    spawn(fun() -> worker_init(foo) end).

worker_init(Strategy) ->
    % Option 1: spawn a "proxy" process
    PriorityProxy = priority_proxy_start(self()),
    erlang:process_flag({priority_marked_message, PriorityProxy}, true),

    % Option 2: allow priority message from any local process
    erlang:process_flag({priority_marked_message, local}, true),

    true = erlang:register(worker, self()),
    worker_loop(Strategy).

worker_loop(Strategy) ->
    receive
        {change_strategy, NewStrategy} ->
            worker_loop(NewStrategy)
    end.

priority_proxy_start(Worker) ->
    spawn(fun() -> priority_proxy_init(Worker) end).

priority_proxy_init(Worker) ->
    true = erlang:register(priority_proxy, self()),
    priority_proxy_loop(Worker).

priority_proxy_loop(Worker) ->
    receive
        Message ->
            erlang:send(Worker, Message, [priority])
            priority_proxy_loop(Worker)
    end.

change_strategy(Strategy) ->
    spawn(fun() ->
        % Option 1: send to a "proxy" process
        erlang:send(priority_proxy, {change_strategy, Strategy}, [priority])
        
        % Option 2: send directly to the worker process
        erlang:send(worker, {change_strategy, Strategy}, [priority])
    end).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that @Maria-12648430's suggestion could solve this by distributing a priority alias to the processes that should be able to send such priority messages.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checking my understanding, that idea might look like this, right?

% receiver
PriorityAlias = erlang:alias([priority]).
% distribute to "senders" out of band

% sender (any process)
PriorityAlias ! priority_message_here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped a longer comment on the Erlang Forum but I worry that relying on runtime behaviour, such as aliasing and distribution of said aliases, to control the priority messages may complicate correctness analysis in the future, or even general understanding of the code. I'd generally strive to make it clearer both in the sender and the receiver that a given message is a priority.

@essen
Copy link

essen commented Jan 8, 2025

If you send priority messages fast enough, the process may never get to read normal messages again. This warrants a big warning in the documentation, or to make the implementation read from both queues. Considering the intended use case, the former sounds good enough.

@rickard-green
Copy link
Contributor Author

If you send priority messages fast enough, the process may never get to read normal messages again. This warrants a big warning in the documentation, or to make the implementation read from both queues. Considering the intended use case, the former sounds good enough.

Yes, but I'd argue that you are misusing the feature if that should happen and quite a lot of features can destroy a system if misused (for example process priorities). Agree about the warning.

eeps/eep-0076.md Outdated
Comment on lines 249 to 254
A separate priority message queue per process exposed to the Erlang program
could be an alternative solution. You would need a way similar to this
proposal to choose which messages should be accepted as priority messages.
There would also need to be some new syntax in order to multiplex matching of
messages from the different message queues. This would be a larger change of
the language without providing any extra benefits as I see it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I suggested in erlang/otp#8371 (comment), a separate mailbox could lead a way to a very trivial implementation of a worker pool - multiple workers receiving from a shared mailbox. In general, MPMC queues would be quite useful, as they are widely used in the industry (assuming current mailboxes are MPSC queues).
Arguably, it also makes the ordering semantics much simpler - the order is maintained within a mailbox, and switching mailboxes for a process would be explicit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea... but also a massive change, not only in terms of behind-the-scenes changes but also in how a user would have to think about messages (or signals) in general. Generally, taking into account "making message queues a first-class object" as said in erlang/otp#8371 (comment), it would amount to "process B can "steal" messages from the inbox of process A", something that you definitely have to get used to after years (decades?) of being an Erlang user.

If you want to have MPMC queues, baked into the language or not, I think they have to be something distinctly different from process message (signal) queues. Ie, you can't replace one with the other, at least not without massive repercussions.

I guess what I'm trying so say is that the fact that it does make a particular use case trivial as you put it, doesn't mean that it is a generally good idea, in the wider sense. Put differently, it is IMO a different idea suited for a different use case, and therefore outside of the scope of this EEP.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're going to have a shared mailbox that you need to switch to, then it can just be a new module that takes care of all implementation details. Today we have processes and tables; tomorrow we could have a third type of object that is a mailbox, or a queue, that multiple processes could use. It could have semantics similar to Erlang process mailboxes, only shared (you wouldn't replace RabbitMQ with it, but it could help in various node-local scenarios). I don't think it requires special syntax, only considerations around how best to get messages from these queues.

@rickard-green
Copy link
Contributor Author

rickard-green commented Jan 22, 2025

I've updated the EEP. The process_flag/2 flags for identifying messages to handle as priority messages have been replaced by priority aliases and priority options.

@rickard-green rickard-green merged commit c0d4e56 into erlang:master Jan 22, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants