Refresh scheduling on specific days of the month

The directions in the refresh schedule documentation only specify a daily time window.

How can the refresh be set to a particular day of the month or day of the week between specific times?

What is the command to reset the refresh schedule to the default SNAPS refresh schedule behavior?

Is this feature persistent through Ubuntu server system reboots?

Hello, and sorry for taking a while to get back to you.

Currently there’s no way to do it, but the scheduling feature is being improved to support exactly that. The syntax supported since snapd 2.25 is described in the core configuration options topic,

Per that existing syntax, the dash (-) character separates ranges, and the slash (/) separates independent windows which are summed up. Some examples:

  • 9:00 – Every day starting within 9-10am.
  • 9:00-11:00 – Every day starting within 9-11am.
  • 9:00-11:00/14:00-16:00 – Every day starting within 9-11am and 2-4pm.

I propose we extend this syntax so that in addition to the dash ranges and slashed sets, we add support for the at symbol (@) defining the day of the week in which a set is applied, and the comma (,) separating options within a given day. The weekday would be defined by the English three-letter prefix, potentially suffixed by a number that defines the week within the month.

Some examples of the proposed extensions:

  • mon-fri@9:00-11:00 – Monday to Friday, starting within 9-11am.
  • tue,fri@9:00-11:00,14:00-16:00 – Every Tuesday and Friday, starting within 9-11am and 2-4pm.
  • tue1,tue3@9:00 – Every first and third Tuesday of the month, starting within 9-11am.

What do you think?

Yes this would provide more control then exists in SNAPS 2.25 today. Scheduling one day a month to deal with a broken update is manageable for this sysadmin. This chat system is critical for my users. Failed updates like the 671 to 801 that happened most recently caused this system to be down for 2 days before it could be dealt with.

When I execute
-# snap set core refresh.schedule=7:31-8:31

This is persistent through server reboots, correct? Or does this command need to be run from crontab each time the server on which SNAPS resides reboots?

Ubuntu 16.04.2 LTS AMD64 Server

@skidmark Thanks for the feedback, and happy that this will address your use case well.

Yes, the snap set command changes the configuration of the snap as is supported now for any snap via the configuration hook, and those configurations are saved in a persistent way, so you only need to set it once.

Just a quick update here: the syntax suggested above will change. We’ll use the same syntax for both service timers, refresh scheduling, and anything else related, so this will need some further thinking. I’ve covered some ideas with @mvo, but we need some more discussion before settling on a syntax.

After some extensive and independent bikeshedding on the syntax, what follows below is a proposal for how the new syntax might look like and behave. But first, the high-level goals that the proposal attempts to achieve:

Goals

  • Good readability, although some initial familiarity may be required
  • Good usability in the command line (no escaping, no quoting)
  • Support for time windows rather than just individual times
  • Support for fuzzing inside time windows
  • Support for terse and readable recurring events
  • Comfortable scheduling of weekly and monthly events

Semantics

A timer string is composed of one or more event lists.

Each event list defines the weekdays and the time windows in which events may occur. The next event will be scheduled inside the soonest opportunity that matches both one of the provided weekdays and one of the provided time windows. If no weekdays are provided, the default is every day. If no time windows are provided, the default is an arbitrary time in the day.

For example, consider the timer:

  • mon,fri,10:00,15:00

Assuming today is Sunday, the next 5 events are, in order:

  • Monday 10am
  • Monday 15pm
  • Friday 10am
  • Friday 15pm
  • Monday 10am

On the other hand, consider the slightly different timer:

  • mon,10:00..fri,15:00

The next 3 events in this case are:

  • Monday 10am
  • Friday 15pm
  • Monday 10am

All of these examples work on a weekly basis, but certain events are better scheduled on a monthly basis. To support that, weekdays may be suffixed by a number that defines the week number inside the month. As an example, the following timer defines two events every month, on the first and third Mondays at 3pm:

  • mon1,mon3,15:00

As a special case, the 5th week is considered the last one to hold the given day, so that specifying an event on the last Friday of the month, for instance, is done simply as fri5.

In addition to specifying precise weekdays, an interval may be used to define a larger span:

  • mon-fri,15:00

This represents an event per day at 15:00, every workday of the week.

The same syntax also works to define time spans, but the meaning is slightly different. For instance, consider this time span:

  • mon,14:00-16:00

It defines an event every Monday that will take place at the earliest chance between 2pm and 4pm. Note the difference in meaning between weekday spans and time spans: weekday spans define an event every day within the span, while time spans define a single event inside the defined span.

That inconsistency may sound uncomfortable from a design perspective, but it’s an important aspect that preserves the readability of these intervals since the most natural way to read mon-fri is “Monday to Friday”, and when people say that they really mean every day. On the other hand, when people are talking about times, the most natural way to read 14:00-16:00 is actually “between 2pm and 4pm”, which represents a single event.

That latter aspect may be changed via an explicit divisor, though, which may be specified either as a count or as a time string. For instance, consider this time span:

  • 8:00-16:00/2

This represents two events every day, one in the morning between 8am and 12pm, and another one between 12pm and 16pm.

Specifying the divisor as a time string works as expected. These two timers mean exactly the same thing, for instance:

  • 0:00-24:00/1:00
  • 0:00-24:00/24

They both represent an event every hour every day.

Dividing the whole day in N equal parts is such a common task that there’s a shorthand notation for it. The above time spans are exactly equivalent to:

  • -/1:00
  • -/24

All of the time spans defined so far work similarly in the sense that the start of the span defines the earliest chance in which the event may start, and the end of the span defines the latest chance for the event to have started. For various reasons, though, it’s often useful to introduce some level of randomization inside the time span so that events won’t all start at exactly the same time. This may be achieved by replacing the time span dash character ("-") by a tilde ("~"). For instance:

  • 0:00~24:00/6:00
  • 0:00~24:00/4
  • ~/6:00

All of these examples specify the same schedule: 4 events that will take place at a random time inside time spans of 6 hours each.

ABNF syntax

The ABNF definition for the proposed syntax follows below:

(note: strings are case sensitive)

eventlist = eventset *( ".." eventset )
eventset = wdaylist / timelist / wdaylist "," timelist

wdaylist = wdayset *( "," wdayset )
wdayset = wday / wdayspan
wday =  ( "sun" / "mon" / "tue" / "wed" / "thu" / "fri" / "sat" ) [ DIGIT ]
wdayspan = wday "-" wday

timelist = timeset *( "," timeset )
timeset = time / timespan
time = 2DIGIT ":" 2DIGIT
timespan = time ( "-" / "~" ) time [ "/" ( time / count ) ]
count = 1*DIGIT

Refresh configuration option

The proposed syntax is ambiguous with the existing syntax, so we cannot reuse the same configuration setting for it. Some reasonable names for it are “calendar”, “agenda”, or “timer”. That last one seems to be the easiest to use in separate contexts. For example, we’re about to introduce timer services as a follow up from the monthly scheduling work, and they will use the same syntax. It seems natural to have a “timer” field that may be defined using the syntax above. If we follow on with that, I also suggest we use “refresh.timer”. for the core configuration option, and that we then change the “–time” flag in the “snap refresh” command to be “–timer” as well (preserving the old one hidden for compatibility), and its output to mention “timer:” instead of “schedule:”.

A quick check list with those proposals, so we don’t get lost:

:white_medium_small_square: Replace “refresh.schedule” with “refresh.timer”, preserving the old one for compatibility. :white_medium_small_square: Replace “snap refresh --time” by “snap refresh --timer”, preserving the old one for compatibility (hidden) :white_medium_small_square: Replace “schedule:” in the output of “snap refresh --timer” by “timer:”, preserving the old one for compatibility if the old setting is present only and --time (no r) was used! :white_medium_small_square: Introduce timer services with “timer:” under the application yaml.

Comments

How does that sound?

I found this example a bit confusing.

Also, the 1st, 3rd days of the month may be a bit confusing. For instance, there are 5 Wednesdays in November and only 4 in December. Adding a timer at mon5 (5th monday) makes it unresolvable in December, but quite right in November. I would prefer to drop this notation altogether.

Maybe we could settle on something like this:

mon,10:00-15:00   # mondays, anywhere between 10am and 3pm
10:00-15:00       # every day between 10am and 3pm
-,10:00-15:00     # same as above
10:00-15:00/2     # twice, every day, between 10am and 3pm
jan,-,10:00       # january, every day, at 10
12,10:00-15:00    # every month on the 12th, between 10am and 3pm
month/2           # twice a month
month,15          # every month on the 15th
year/12,mon,24:00 # 12 times a year, i.e. every month, on mondays at midnight, same 
                  # as mon,24:00
-/24:00           # every 24h
~/6:00            # randomized, every 6h
15,mon,10:00      # every Monday the 15th at 10am
15                # every month oh the 15th

I won’t go into details and write out a ABNF spec :), but the syntax boils down to:

  • term[,term..], where , is Prologish ‘and’.
  • Each term is <spec>[-<spec>][/<div>].
  • Each spec is of type hour, week-day, day, month, year with optional span (<spec>-<spec>).
  • Hour is expressed as <hh>:<mm>.
  • Divisor only makes sense if a span is used.
  • There are predefined month specs - jan|feb|mar|apr... , and week-day specs - mon|tue|wed...
  • There are some special keywords, month is equivalent to 1-31, week is mon-fri, year is jan-dec.
  • The specs come in order <year>,<month>,<day>,<week-day>,<hour>

I think it’s pretty close to what you proposed . The only difference I see is that I would prefer to use multiple --timer entries in command line and possibly white space separated entries in configuration to express the times such as mon,fri,10:00,15:00 (actually this one is an extreme case I believe). Whitespace basically acts as an ‘or’ operator. Taking a simpler example:

# every monday between 10am and 3pm & every friday between 10am and 3pm
snap refresh --timer mon,10:00-15:00 --timer fri,10:00-15:00 

Taking this a step further:

# same thing as above
snap refresh --timer 'mon,10:00-15:00 fri,10:00-15:00'

That was specifically addressed in the proposal, hopefully in a clear way:

As for the rest of your proposal, I think the main thing you’re trying to get out of it support for addressing years and months as well, in addition to organizing around weeks inside the month. The proposed syntax is extensible to that, but I’d prefer to not start from it at least, and learn a bit about the use cases once we have the basic features working.

There are multiple reasons for that. The first one is that I have a feeling people don’t really schedule low-level timers so specifically in practice. A production service that has a timer for a service on a single machine to run once over the whole year is an extremely optimistic design. Another reason that backs on the top of the former one is clarity… the less syntax we have, the easier it is to read and to write it.

As for your last point of using spaces and not .. to join the periods, it was addressed in the goals section: it’s much nicer to have a string that people don’t need to remember to quote upfront when they are writing it out, and it also feels like a more clear statement that the timer is composed by the whole. It’s a single timer, not two timers.

One thing I like about your proposal, by the way, is the idea of using an explicit “day” string instead of “-”. It feels more readable.

Read that twice and still found it a bit confusing, an example maybe, just to be sure that I got it right:

    November 2017   
Su Mo Tu We Th Fr  Sa
          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|  <-- Nov 25 is both sa4 and sa5?
26 27 28 29 30       

Allowing to specify ‘last Friday’ actually makes sense, I’m just not sure about fri5.

Syntax wise, I think both proposals are close. Joining periods with .. is fine with me. Personally, I prefer using whitespace (implementation-wise we could probably support both) or having multiple --timer ... arguments in the command line.

I’ll give it another look in the morning, a rested mind may come up with more ideas :slight_smile:

One more thing, somewhat unrelated to the syntax, but more to the execution. Is it possible to inhibit the timer from running? A use case that I’ve seen come up before is that the host/device is performing some critical task and while it’s being done no other functions that may have a negative impact (performance wise or just generally operation wise) are allowed. Should I bring that up in another thread perhaps?

Yeah, that’s it.

Definitely a special case. The rationale is this: there’s absolutely no point in having something that schedules an event for a weekday that will only recur rarely and in non-interesting situations. There is value however in the notion of the last one.

So, if we support a fri1, 2, 3, and 4 meaning those respective weeks, which sounds like a good idea, then supporting 5 as meaning the last one is the only reasonable option for that. The other option would be to not have it at all, or to have a different syntax altogether. Could not think of something better myself, yet at least.

Supporting both is the worst option, because then everybody has to remember that both mean the same thing, and teams will need conversations about something as silly as the preferred timer syntax. Lacking any better ideas or real issues, let’s please stick to the proposal.

We already had some discussions about this, including here in the forum, and yes we’ll want to implement support for snaps to say “not now, please”. But no need to discuss this now… we know we want it, and we know how we’ll do it (hook plus snapctl command). We just won’t do it right now as we have higher priority items in front of it.

The first piece landed in a PR today https://github.com/snapcore/snapd/pull/4269 It’s a parser for the spec that @niemeyer described. See the PR comment for details. It is mostly close to the proposal.

Short list of what’s possible:

  • mon-fri,10:00-15:00 - Mondays to Fridays, between 10am and 3pm
  • mon-fri,10:00-15:00 - same as above, but the event time is randomized within the window
  • fri-mon,10:00-15:00 - Fridays to Mondays, between 10am and 3pm
  • weekend,10:00 - 10am on weekends (weekend is an alias for sat-sun)
  • 23:00-01:00 - every day between 11pm and 1am the next day
  • day/4 - 4 times a day, every day (instead of -/4, day is an alias for 0:00-24:00)
  • mon5,23:00-01:00 - last Monday of the month, from 11pm to 1am the next day
  • mon1-mon2,0:00-6:00 - from the first Monday of the month to the 2nd Monday, from midnight to 6am
  • 23:00-24:00 - from 11pm to midnight (24:00 is the same as 0:00 but it occurs later in the day)
  • mon,3:00-6:00..fri,22:00-0:00, Mondays from 3am to 6am, Fridays from 10pm to midnight
1 Like

Long time lurker here, getting out to bikeshed (sorry!)… but I can’t resist from saying that “..” looks really weird to me.
Probably because I somehow expect it to be a range operator (as in Perl, Ruby etc.), while here it’s not.

Also “,” looks too overloaded (means both “AND” and “AT”), since it separates:

  1. day lists (mon,fri => AND)
  2. time lists (10:00,15:00 => AND)
  3. day from time (mon,10:00 => AT)
  4. day lists from time lists (mon,fri,10:00,15:00 => AND+AT+AND)

The latter I find confusing.

I think both problems could be fixed by using:

  • “@” in place of “,” to separate days from times (case 3 and 4 above)
  • “,” in place of “..

This way the example in PR #4313:

    mon,10:00..fri,15:00 (Monday at 10:00, Friday at 15:00)
    mon,fri,10:00,15:00 (Monday at 10:00 and 15:00, Friday at 10:00 and 15:00)
    mon-wed,fri,9:00-11:00/2 (Monday to Wednesday and on Friday, twice between 9:00 and 11:00)
    mon,9:00~11:00..wed,22:00~23:00 (Monday, sometime between 9:00 and 11:00, and on Wednesday, sometime between 22:00 and 23:00)
    mon,wed  (Monday and on Wednesday)
    mon..wed (same as above)

would become:

    mon@10:00,fri@15:00
    mon,fri@10:00,15:00
    mon-wed,fri@9:00-11:00/2
    mon@9:00~11:00,wed@22:00~23:00
    mon,wed
    mon,wed

Re-reading the thread, this looks like the original proposal by @niemeyer.
I can’t see how the new syntax being implemented is better, but maybe I’m missing something obvious…

2 Likes

We are close to landing the parser bit of the code with 4313. After some discussion we’ve addressed the range feel of .. and decided to go with ,, instead (unfortunately there’s not that many shell-friendly characters left to choose from). This makes the examples look like this:

mon,10:00,,fri,15:00 (Monday at 10:00, Friday at 15:00)
mon,fri,10:00,15:00 (Monday at 10:00 and 15:00, Friday at 10:00 and 15:00)
mon-wed,fri,9:00-11:00/2 (Monday to Wednesday and on Friday, twice between
                          9:00 and 11:00)
mon,9:00~11:00,,wed,22:00~23:00 (Monday, sometime between 9:00 and 11:00, and
                                 on Wednesday, sometime between 22:00 and 23:00)
mon,wed  (Monday and on Wednesday)
mon,,wed (same as above)

Thank you @mborzecki. While I really appreciate you reconsidered the .. token, I still think my proposal above sounds far more natural (@ is shell-safe after all, isn’t it?).
But maybe it’s just me and this is subjective ground, so I won’t push further.

The parser has landed. I already have the code replacing refresh.schedule with refresh.timer but after some thought I need additional clarification from @niemeyer.

# use format compatible with the old parser
$ sudo snap set core refresh.timer=12:00-13:10 
$ sudo snap set core refresh.schedule=""
$ snap refresh --time
schedule: 12:00-13:10 
$ snap refresh --timer
timer: 12:00-13:10 

# incompatible with old parser
$ sudo snap set core refresh.timer=mon,12:00-13:10 
$ sudo snap set core refresh.schedule=""
$ snap refresh --time
schedule: mon,12:00-13:00   # cut-n-paste to refresh.schedule will fail
$ snap refresh --timer
timer: mon,12:00-13:10 

And there’a perculiar case of refresh.schedule=managed. For now I have assumed that refresh.schedule == managed takes priority over refresh.timer.

Edit: proposed changes are in this PR: https://github.com/snapcore/snapd/pull/4476

All of this has landed now.

1 Like

One thing to note is that we currently have a systemd timer unit that does a ‘snap refresh’ once a week. This was placed there when we moved to using the internal timer, in case things got stuck; a system administrator that wants to have refreshes happen on a longer cadence than weekly needs to modify (or disable) this file. A systemctl disable snapd.refresh.timer would accomplish the latter.


There is still value in having this systemd timer as a a fallback, in case a bug betwen snapd and the system clock result in snapd’s refresh timer not working, but snapd needs some changes to be more aware of this.

This’ll also let us stop it looking weird in the changes log.

As we discussed in the standup today, that feels somewhat awkward, and also a bit unexpected in the sense that I remember we disabled this in the past, but it came back in a different way.

Can we just kill that timer for good and ensure the real mechanism works reliably and always?

cc @mborzecki

1 Like

I have made the changes that drop snapd.refresh.timer. I still need to finish reviewing how we use the timer so that we do not run into issues with monotonic timers. If I understand the problem correctly, we should be ok.

One scenario that we probably didn’t think of is when someone is using socket activation of snapd and the snapd.socket is the only service that is activated on boot. Unless someone performs an action that activates snapd, it will remain inactive and none of the refresh times will be hit.