Let messages-per-hour 0 turn the hourly pause off (issue 1476)
mesh/merge-gate head a5d3480e7e5d merged into trunk 59f8c9be0090 — pass: builds messenger → novox; no bus step; every machine composes with the change as i…
mesh/repo-check head a5d3480e7e5d merged into trunk 59f8c9be0090 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

This commit is contained in:
2026-10-20 21:54:25 +02:00
parent dccae4b0e7
commit a5d3480e7e
5 changed files with 46 additions and 13 deletions
@@ -34,10 +34,33 @@ func TestMessagesPerHourIsReadFromTheSettingsFile(t *testing.T) {
}
return readSettings(p)
}
if s, err := read(`{"messages-per-hour": 100}`); err != nil || s.MessagesPerHour != 100 {
if s, err := read(`{"messages-per-hour": 100}`); err != nil || s.MessagesPerHour == nil || *s.MessagesPerHour != 100 {
t.Fatalf("set: %+v %v", s, err)
}
if s, err := read(`{"time-zone": ""}`); err != nil || s.MessagesPerHour != 0 {
if s, err := read(`{"messages-per-hour": 0}`); err != nil || s.MessagesPerHour == nil || *s.MessagesPerHour != 0 {
t.Fatalf("zero: %+v %v", s, err)
}
if s, err := read(`{"time-zone": ""}`); err != nil || s.MessagesPerHour != nil {
t.Fatalf("unset: %+v %v", s, err)
}
}
// 0 turns the pause off (issue 1476): every message goes out and the pause is never said.
func TestACapOfZeroTurnsThePauseOff(t *testing.T) {
h, tg, _, c, _ := newHolder(t)
h.Desk = nil
h.CapPerHour = func() int { return 0 }
for i := 0; i < 500; i++ {
h.Condition(EventRaised, cond(fmt.Sprintf("machine.m%d.silent", i), Urgent, "a machine is silent"))
c.pass(BurstWindow + time.Second)
h.Flush()
}
if len(tg.sends) != 500 {
t.Fatalf("sent %d of 500", len(tg.sends))
}
for _, m := range tg.sends {
if strings.HasPrefix(m.Title, "Mesh messages paused") {
t.Fatalf("the pause was said with the cap off")
}
}
}
+3 -3
View File
@@ -325,7 +325,7 @@ type Holder struct {
// `only-what-needs-me`, ADR 0308 §4); nil, none is.
NeedsOnly func(kind string) bool
// CapPerHour is the most messages one channel gets an hour (the setting messages-per-hour, issue 1476);
// nil, or a value below one, is DefaultCapPerHour. TellCapPerHour is its own and stays fixed.
// nil, or a value below zero, is DefaultCapPerHour; 0 is no cap at all. TellCapPerHour is its own and stays fixed.
CapPerHour func() int
work sync.Mutex // one event, call or tick at a time
@@ -1212,10 +1212,10 @@ func withheldLine(line string, refusal Refusal, passes func(string) (Refusal, bo
return "(a line carrying " + refusal.What + " is withheld here)"
}
// capPerHour is the cap now in force on a channel's messages an hour.
// capPerHour is the cap now in force on a channel's messages an hour; 0 is none.
func (h *Holder) capPerHour() int {
if h.CapPerHour != nil {
if n := h.CapPerHour(); n >= 1 {
if n := h.CapPerHour(); n >= 0 {
return n
}
}
+14 -5
View File
@@ -33,8 +33,8 @@ type Settings struct {
// with nothing held and nothing open is not told there at all (novox/hq ADR 0308 §4).
OnlyWhatNeedsMe []string `json:"only-what-needs-me"`
// MessagesPerHour is the most messages one channel gets in an hour (issue 1476); 0, not given, is
// DefaultCapPerHour.
MessagesPerHour int `json:"messages-per-hour"`
// DefaultCapPerHour; 0 is no cap at all.
MessagesPerHour *int `json:"messages-per-hour"`
}
// zoneOf is the location a time-zone setting names, or this machine's own when it names none or one
@@ -69,7 +69,11 @@ func readSettings(path string) (Settings, error) {
switch v := loose["messages-per-hour"].(type) {
case nil:
case float64:
s.MessagesPerHour = int(v)
n := int(v)
if v < 0 || v != float64(n) {
return s, fmt.Errorf("messages-per-hour is not a whole number of zero or more")
}
s.MessagesPerHour = &n
default:
return s, fmt.Errorf("messages-per-hour is not a number")
}
@@ -340,8 +344,13 @@ func main() {
}
return loc
},
Emit: func(event string, body any) error { return stdio.Emit(event, body) },
CapPerHour: func() int { return settings().MessagesPerHour },
Emit: func(event string, body any) error { return stdio.Emit(event, body) },
CapPerHour: func() int {
if n := settings().MessagesPerHour; n != nil {
return *n
}
return DefaultCapPerHour
},
NeedsOnly: func(kind string) bool {
for _, k := range settings().OnlyWhatNeedsMe {
if k == kind {
+2 -1
View File
@@ -613,7 +613,8 @@ func (h *Holder) sendNow(ch Channel, key, what string, m Message) bool {
// tell is not counted here: it has its own cap (TellCapPerHour) so a burst of tells cannot crowd
// out a condition or an ask (ADR 0380 §8).
func (h *Holder) allow(channel string) bool {
return h.sentLastHour(channel) < h.capPerHour()
n := h.capPerHour()
return n == 0 || h.sentLastHour(channel) < n
}
func (h *Holder) sentLastHour(channel string) int {
+2 -2
View File
@@ -135,8 +135,8 @@
},
"messages-per-hour": {
"type": "number",
"description": "The most messages the mesh sends one channel in an hour, in messages per channel per hour; past it the rest are held and said once as one message (not the tell verb, which has its own cap of 10).",
"minimum": 1,
"description": "messages per channel per hour before the rest are held as one; 0 turns the pause off. The tell verb has its own cap of 10 and is not counted here.",
"minimum": 0,
"maximum": 1000,
"trusted": false,
"default": 100