carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package activator
2
3import (
4 "errors"
5 "os"
6
7 "github.com/carlosframework/platform/internal/registry"
8)
9
10// canonlink.go keeps a route's canonical socket path (rt.Socket) pointing at
11// its current generation, for processes outside the edge and the agent that
12// dial it, such as members of the carlos-sockets group (#103; zero-downtime
13// spec §7.1, rev 15). Unix connect() follows the symlink. Internal dials
14// never use it: they go through AppSocket or a handle, and unixdial.Checked
15// would refuse the link anyway (the listener's bound path is the generation
16// path, not the dialled one).
17//
18// Plan 1 creates the link when the first generation becomes current and
19// removes it when the entry stops. Plan 3 calls swapCanonicalLink at every
20// switch.
21
22// linksCanonical reports whether g's process is reached through a symlink at
23// rt's canonical path: only when the generation bound a path of its own. A
24// generation on the canonical socket itself (generations off, a unit tenant,
25// a foreign-layout row) is never linked: the link would replace the real
26// socket.
27func linksCanonical(rt registry.Route, g Gen) bool {
28 return rt.Socket != "" && g.Socket != "" && g.Socket != rt.Socket
29}
30
31// swapCanonicalLink points canonical at target atomically: a temporary link
32// beside it, then rename over it. A dialler sees the old target or the new
33// one, never no path. Whatever was at canonical (an earlier link, or a stale
34// socket file from before generations) is replaced.
35func swapCanonicalLink(canonical, target string) error {
36 tmp := canonical + ".lnk"
37 if err := os.Remove(tmp); err != nil && !errors.Is(err, os.ErrNotExist) {
38 return err
39 }
40 if err := os.Symlink(target, tmp); err != nil {
41 return err
42 }
43 if err := os.Rename(tmp, canonical); err != nil {
44 _ = os.Remove(tmp)
45 return err
46 }
47 return nil
48}
49
50// removeCanonicalLink removes canonical only while it is a symlink to
51// target: a link a later generation (or a later entry) has already moved,
52// and a real socket, are left alone.
53func removeCanonicalLink(canonical, target string) error {
54 got, err := os.Readlink(canonical)
55 if err != nil || got != target {
56 return nil
57 }
58 if err := os.Remove(canonical); err != nil && !errors.Is(err, os.ErrNotExist) {
59 return err
60 }
61 return nil
62}
63
64// clearLeftoverCanonicalLink removes a symlink at canonical, whatever it
65// names, and nothing else. An agent with generations on that died without
66// stopping its entries leaves one; an app told to bind canonical with the
67// switch off would otherwise fail with "address in use". A canonical path
68// that is not a symlink (every box that never ran generations) is not
69// touched.
70func clearLeftoverCanonicalLink(canonical string) error {
71 fi, err := os.Lstat(canonical)
72 if err != nil || fi.Mode()&os.ModeSymlink == 0 {
73 return nil
74 }
75 if err := os.Remove(canonical); err != nil && !errors.Is(err, os.ErrNotExist) {
76 return err
77 }
78 return nil
79}
80