YmerNode.Messages.Threads (Ymer Node v0.7.0)

Copy Markdown View Source

What YmerNode.Messages holds — every kept message by thread, and each handle's read mark, last send and last check on every thread it has taken part in — as a value, with the functions that decide what a send, a check and a read do to it.

The process is the clock around this module: every function here is handed the time it runs at, and the page it answers within, so each rule below is a deterministic function a test drives with a count cap of three rather than ten thousand.

Ids and the count cap

Every message takes the next id across all threads, so ids order the whole history and a read's cursor needs no thread beside it. Past the count cap the oldest message is dropped, whichever thread holds it. The thread remembers the newest message it lost, and that is how a check whose read mark sits before it, or a read that reaches back to the oldest message kept, can say that messages were dropped.

Read marks

Each handle's read mark on a thread is kept here as a message id. check/5 returns what lies past it, oldest first, leaves out the handle's own messages, and moves the mark past everything it walked — its own messages among them, so they never count as unread. A check also joins the thread: a thread nobody has posted to yet exists from its first check, so a session waiting on it shows in the thread's header before anything arrives.

Pages

A page stops at its message count or its byte budget, whichever comes first, and always holds at least one message, so paging always moves. What a message weighs is the page's size function — YmerNode.Messages measures each message as it will be sent.

read/4 moves no mark. It merges the named threads' messages newest first, takes the newest last of them across all those threads together, and cuts the page from the top. Every message last or the page leaves out is older than every message kept, so the oldest id returned is the cursor to pass back as before, and it is given while any named thread keeps an older message. That is why last is never taken thread by thread: a busy thread's cut messages would then sit above a quieter thread's oldest returned one, and a cursor below them would skip them for good.

Summary

Types

How many messages a page holds, how many bytes, and what one weighs.

t()

Functions

Checks thread as handle at now, within page. Returns the answer and the state with the handle's read mark moved.

A thread's header: its message count, last activity, and each handle with its last send and last check, by handle — nil for a thread the process has never seen.

One row per thread — its message count, last activity and handles — the most recently active first, threads nobody has posted to last.

Whether handle has sent on, or checked, any thread.

Posts body to thread as handle at now. Returns the message and the state it is kept in — past the count cap, without the oldest message held.

Reads threads — a list of names, or :all for every thread — within filters and page, moving no mark.

The threads handle has taken part in that hold messages it has not read, each with the count of them, by thread. Its own messages are never unread.

Types

page()

@type page() :: %{
  count: pos_integer(),
  bytes: pos_integer(),
  size: (YmerNode.Messages.Message.t() -> non_neg_integer())
}

How many messages a page holds, how many bytes, and what one weighs.

t()

@type t() :: %YmerNode.Messages.Threads{
  cap: term(),
  count: term(),
  next_id: term(),
  order: term(),
  threads: term()
}

Functions

check(state, handle, thread, now, page)

Checks thread as handle at now, within page. Returns the answer and the state with the handle's read mark moved.

The answer's messages are oldest first; more counts the unread left past the page; last_check is the handle's previous check of this thread, nil on its first; posted? says whether anything was ever posted to the thread; and dropped_at is the time of the newest message the thread lost to the count cap, given only when the handle's read mark sits before it.

header(state, thread)

A thread's header: its message count, last activity, and each handle with its last send and last check, by handle — nil for a thread the process has never seen.

index(state)

One row per thread — its message count, last activity and handles — the most recently active first, threads nobody has posted to last.

joined?(state, handle)

Whether handle has sent on, or checked, any thread.

new(cap)

post(state, handle, thread, body, now)

Posts body to thread as handle at now. Returns the message and the state it is kept in — past the count cap, without the oldest message held.

read(state, threads, map, page)

Reads threads — a list of names, or :all for every thread — within filters and page, moving no mark.

filters holds last, the newest messages taken across the named threads together; before, an id every message returned lies below, or nil; and since, a UTC time every message returned was posted at or after, or nil. Each thread's answer carries its header — nil for a thread the process has never seen — its messages newest first; matched, how many of the messages it keeps fall within before and since, so an answer with none can say whether the filter or the page left them out; and dropped_at when the read reached back to the oldest message the thread keeps and the count cap had dropped messages before it. Under :all only the threads with a message to return, or a drop to report, answer.

unread(state, handle)

The threads handle has taken part in that hold messages it has not read, each with the count of them, by thread. Its own messages are never unread.