detached 26d3440

fix(core): claim proxies atomically before sending destructors

commit
26d3440eb60a9e7c47e534f875424b8f753ff300
author
brice · · 2026-09-28 07:44 UTC
parent
60778dc
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +95 -41
M core.go +29 -0
@@ -89,6 +89,35 @@ func (c *Context) CheckProxy(proxy Proxy) error {
8989 return nil
9090 }
9191
92+// SendDestructor sends a destructor request exactly once. The proxy is
93+// unregistered atomically before sending, so concurrent callers cannot both
94+// write it; the losers receive an error and nothing reaches the wire.
95+func (c *Context) SendDestructor(proxy Proxy, opcode uint32, args ...interface{}) error {
96+ if err := c.claimDestroy(proxy); err != nil {
97+ return err
98+ }
99+ return c.display.SendRequest(proxy.ID(), uint16(opcode), args...)
100+}
101+
102+// SendDestructorWithFDs is SendDestructor for requests carrying FDs.
103+func (c *Context) SendDestructorWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
104+ if err := c.claimDestroy(proxy); err != nil {
105+ return err
106+ }
107+ return c.display.SendRequestWithFDs(proxy.ID(), uint16(opcode), fds, args...)
108+}
109+
110+func (c *Context) claimDestroy(proxy Proxy) error {
111+ if err := c.CheckProxy(proxy); err != nil {
112+ return err
113+ }
114+ if !c.proxies.CompareAndDelete(proxy.ID(), proxy) {
115+ return errors.New("proxy is not registered")
116+ }
117+ c.display.objects.CompareAndDelete(proxy.ID(), proxy)
118+ return nil
119+}
120+
92121 // Register registers a proxy object
93122 func (c *Context) Register(proxy Proxy) {
94123 if proxy != nil && proxy.ID() != 0 && !c.closed.Load() {
@@ -89,6 +89,35 @@ func (c *Context) CheckProxy(proxy Proxy) error {
89 return nil89 return nil
90 }90 }
91 91
92+// SendDestructor sends a destructor request exactly once. The proxy is
93+// unregistered atomically before sending, so concurrent callers cannot both
94+// write it; the losers receive an error and nothing reaches the wire.
95+func (c *Context) SendDestructor(proxy Proxy, opcode uint32, args ...interface{}) error {
96+ if err := c.claimDestroy(proxy); err != nil {
97+ return err
98+ }
99+ return c.display.SendRequest(proxy.ID(), uint16(opcode), args...)
100+}
101+
102+// SendDestructorWithFDs is SendDestructor for requests carrying FDs.
103+func (c *Context) SendDestructorWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
104+ if err := c.claimDestroy(proxy); err != nil {
105+ return err
106+ }
107+ return c.display.SendRequestWithFDs(proxy.ID(), uint16(opcode), fds, args...)
108+}
109+
110+func (c *Context) claimDestroy(proxy Proxy) error {
111+ if err := c.CheckProxy(proxy); err != nil {
112+ return err
113+ }
114+ if !c.proxies.CompareAndDelete(proxy.ID(), proxy) {
115+ return errors.New("proxy is not registered")
116+ }
117+ c.display.objects.CompareAndDelete(proxy.ID(), proxy)
118+ return nil
119+}
120+
92 // Register registers a proxy object121 // Register registers a proxy object
93 func (c *Context) Register(proxy Proxy) {122 func (c *Context) Register(proxy Proxy) {
94 if proxy != nil && proxy.ID() != 0 && !c.closed.Load() {123 if proxy != nil && proxy.ID() != 0 && !c.closed.Load() {
M internal/scanner/scanner.go +8 -2
@@ -490,11 +490,17 @@ func (s *Scanner) processRequest(iface Interface, req Request, opcode int) (requ
490490 if len(argExprs) > 0 {
491491 args = ", " + strings.Join(argExprs, ", ")
492492 }
493+ // Destructors claim the proxy atomically before sending so a concurrent
494+ // second destroy cannot reach the wire.
495+ send := "SendRequest"
496+ if data.Destructor {
497+ send = "SendDestructor"
498+ }
493499 if len(fdExprs) > 0 {
494500 data.HasFDs = true
495- data.SendCall = fmt.Sprintf("SendRequestWithFDs(o, %d, []int{%s}%s)", opcode, strings.Join(fdExprs, ", "), args)
501+ data.SendCall = fmt.Sprintf("%sWithFDs(o, %d, []int{%s}%s)", send, opcode, strings.Join(fdExprs, ", "), args)
496502 } else {
497- data.SendCall = fmt.Sprintf("SendRequest(o, %d%s)", opcode, args)
503+ data.SendCall = fmt.Sprintf("%s(o, %d%s)", send, opcode, args)
498504 }
499505 if data.CreatesChild {
500506 data.Results = "(*" + data.ChildType + ", error)"
@@ -490,11 +490,17 @@ func (s *Scanner) processRequest(iface Interface, req Request, opcode int) (requ
490 if len(argExprs) > 0 {490 if len(argExprs) > 0 {
491 args = ", " + strings.Join(argExprs, ", ")491 args = ", " + strings.Join(argExprs, ", ")
492 }492 }
493+ // Destructors claim the proxy atomically before sending so a concurrent
494+ // second destroy cannot reach the wire.
495+ send := "SendRequest"
496+ if data.Destructor {
497+ send = "SendDestructor"
498+ }
493 if len(fdExprs) > 0 {499 if len(fdExprs) > 0 {
494 data.HasFDs = true500 data.HasFDs = true
495- data.SendCall = fmt.Sprintf("SendRequestWithFDs(o, %d, []int{%s}%s)", opcode, strings.Join(fdExprs, ", "), args)501+ data.SendCall = fmt.Sprintf("%sWithFDs(o, %d, []int{%s}%s)", send, opcode, strings.Join(fdExprs, ", "), args)
496 } else {502 } else {
497- data.SendCall = fmt.Sprintf("SendRequest(o, %d%s)", opcode, args)503+ data.SendCall = fmt.Sprintf("%s(o, %d%s)", send, opcode, args)
498 }504 }
499 if data.CreatesChild {505 if data.CreatesChild {
500 data.Results = "(*" + data.ChildType + ", error)"506 data.Results = "(*" + data.ChildType + ", error)"
M internal/scanner/template.go +0 -3
@@ -73,9 +73,6 @@ if err := o.Context().CheckProxy(o); err != nil { return {{.ErrorReturn}} }
7373 {{- range .FDArgs}}
7474 _ = wl.CloseSentFD({{.}})
7575 {{- end}}
76-{{- if .Destructor}}
77- o.Context().Unregister(o)
78-{{- end}}
7976 {{- if .CreatesChild}}
8077 return {{.ChildVar}}, nil
8178 {{- else}}
@@ -73,9 +73,6 @@ if err := o.Context().CheckProxy(o); err != nil { return {{.ErrorReturn}} }
73 {{- range .FDArgs}}73 {{- range .FDArgs}}
74 _ = wl.CloseSentFD({{.}})74 _ = wl.CloseSentFD({{.}})
75 {{- end}}75 {{- end}}
76-{{- if .Destructor}}
77- o.Context().Unregister(o)
78-{{- end}}
79 {{- if .CreatesChild}}76 {{- if .CreatesChild}}
80 return {{.ChildVar}}, nil77 return {{.ChildVar}}, nil
81 {{- else}}78 {{- else}}
M protocol/core/core_test.go +40 -0
@@ -431,3 +431,43 @@ func TestDestroyedProxyRejectsRequests(t *testing.T) {
431431 t.Fatalf("unexpected bytes on wire: %v", err)
432432 }
433433 }
434+
435+func TestConcurrentDestroySendsOnce(t *testing.T) {
436+ c, p := pair(t)
437+ d, err := wlturbo.ConnectFromConn(c)
438+ if err != nil {
439+ t.Fatal(err)
440+ }
441+ defer d.Close()
442+ receiveRequest(t, p)
443+ for round := 0; round < 200; round++ {
444+ surface := core.NewSurface(d.Context())
445+ surface.SetID(d.AllocateID())
446+ d.Context().Register(surface)
447+ const callers = 8
448+ start := make(chan struct{})
449+ results := make(chan error, callers)
450+ for i := 0; i < callers; i++ {
451+ go func() {
452+ <-start
453+ results <- surface.Destroy()
454+ }()
455+ }
456+ close(start)
457+ ok := 0
458+ for i := 0; i < callers; i++ {
459+ if <-results == nil {
460+ ok++
461+ }
462+ }
463+ if ok != 1 {
464+ t.Fatalf("round %d: %d destroys succeeded, want 1", round, ok)
465+ }
466+ receiveRequest(t, p)
467+ p.SetReadDeadline(time.Now().Add(2 * time.Millisecond))
468+ if n, err := p.Read(make([]byte, 8)); n != 0 {
469+ t.Fatalf("round %d: extra destroy on wire (%d bytes, %v)", round, n, err)
470+ }
471+ p.SetReadDeadline(time.Time{})
472+ }
473+}
@@ -431,3 +431,43 @@ func TestDestroyedProxyRejectsRequests(t *testing.T) {
431 t.Fatalf("unexpected bytes on wire: %v", err)431 t.Fatalf("unexpected bytes on wire: %v", err)
432 }432 }
433 }433 }
434+
435+func TestConcurrentDestroySendsOnce(t *testing.T) {
436+ c, p := pair(t)
437+ d, err := wlturbo.ConnectFromConn(c)
438+ if err != nil {
439+ t.Fatal(err)
440+ }
441+ defer d.Close()
442+ receiveRequest(t, p)
443+ for round := 0; round < 200; round++ {
444+ surface := core.NewSurface(d.Context())
445+ surface.SetID(d.AllocateID())
446+ d.Context().Register(surface)
447+ const callers = 8
448+ start := make(chan struct{})
449+ results := make(chan error, callers)
450+ for i := 0; i < callers; i++ {
451+ go func() {
452+ <-start
453+ results <- surface.Destroy()
454+ }()
455+ }
456+ close(start)
457+ ok := 0
458+ for i := 0; i < callers; i++ {
459+ if <-results == nil {
460+ ok++
461+ }
462+ }
463+ if ok != 1 {
464+ t.Fatalf("round %d: %d destroys succeeded, want 1", round, ok)
465+ }
466+ receiveRequest(t, p)
467+ p.SetReadDeadline(time.Now().Add(2 * time.Millisecond))
468+ if n, err := p.Read(make([]byte, 8)); n != 0 {
469+ t.Fatalf("round %d: extra destroy on wire (%d bytes, %v)", round, n, err)
470+ }
471+ p.SetReadDeadline(time.Time{})
472+ }
473+}
M protocol/core/wayland_generated.go +18 -36
@@ -808,10 +808,9 @@ func (o *Compositor) Release() error {
808808 if err := o.Context().CheckProxy(o); err != nil {
809809 return err
810810 }
811- if err := o.Context().SendRequest(o, 2); err != nil {
811+ if err := o.Context().SendDestructor(o, 2); err != nil {
812812 return err
813813 }
814- o.Context().Unregister(o)
815814 return nil
816815 }
817816
@@ -867,10 +866,9 @@ func (o *ShmPool) Destroy() error {
867866 if err := o.Context().CheckProxy(o); err != nil {
868867 return err
869868 }
870- if err := o.Context().SendRequest(o, 1); err != nil {
869+ if err := o.Context().SendDestructor(o, 1); err != nil {
871870 return err
872871 }
873- o.Context().Unregister(o)
874872 return nil
875873 }
876874
@@ -942,10 +940,9 @@ func (o *Shm) Release() error {
942940 if err := o.Context().CheckProxy(o); err != nil {
943941 return err
944942 }
945- if err := o.Context().SendRequest(o, 1); err != nil {
943+ if err := o.Context().SendDestructor(o, 1); err != nil {
946944 return err
947945 }
948- o.Context().Unregister(o)
949946 return nil
950947 }
951948
@@ -1020,10 +1017,9 @@ func (o *Buffer) Destroy() error {
10201017 if err := o.Context().CheckProxy(o); err != nil {
10211018 return err
10221019 }
1023- if err := o.Context().SendRequest(o, 0); err != nil {
1020+ if err := o.Context().SendDestructor(o, 0); err != nil {
10241021 return err
10251022 }
1026- o.Context().Unregister(o)
10271023 return nil
10281024 }
10291025
@@ -1122,10 +1118,9 @@ func (o *DataOffer) Destroy() error {
11221118 if err := o.Context().CheckProxy(o); err != nil {
11231119 return err
11241120 }
1125- if err := o.Context().SendRequest(o, 2); err != nil {
1121+ if err := o.Context().SendDestructor(o, 2); err != nil {
11261122 return err
11271123 }
1128- o.Context().Unregister(o)
11291124 return nil
11301125 }
11311126
@@ -1300,10 +1295,9 @@ func (o *DataSource) Destroy() error {
13001295 if err := o.Context().CheckProxy(o); err != nil {
13011296 return err
13021297 }
1303- if err := o.Context().SendRequest(o, 1); err != nil {
1298+ if err := o.Context().SendDestructor(o, 1); err != nil {
13041299 return err
13051300 }
1306- o.Context().Unregister(o)
13071301 return nil
13081302 }
13091303
@@ -1589,10 +1583,9 @@ func (o *DataDevice) Release() error {
15891583 if err := o.Context().CheckProxy(o); err != nil {
15901584 return err
15911585 }
1592- if err := o.Context().SendRequest(o, 2); err != nil {
1586+ if err := o.Context().SendDestructor(o, 2); err != nil {
15931587 return err
15941588 }
1595- o.Context().Unregister(o)
15961589 return nil
15971590 }
15981591
@@ -1862,10 +1855,9 @@ func (o *DataDeviceManager) Release() error {
18621855 if err := o.Context().CheckProxy(o); err != nil {
18631856 return err
18641857 }
1865- if err := o.Context().SendRequest(o, 2); err != nil {
1858+ if err := o.Context().SendDestructor(o, 2); err != nil {
18661859 return err
18671860 }
1868- o.Context().Unregister(o)
18691861 return nil
18701862 }
18711863
@@ -2232,10 +2224,9 @@ func (o *Surface) Destroy() error {
22322224 if err := o.Context().CheckProxy(o); err != nil {
22332225 return err
22342226 }
2235- if err := o.Context().SendRequest(o, 0); err != nil {
2227+ if err := o.Context().SendDestructor(o, 0); err != nil {
22362228 return err
22372229 }
2238- o.Context().Unregister(o)
22392230 return nil
22402231 }
22412232
@@ -2595,10 +2586,9 @@ func (o *Seat) Release() error {
25952586 if err := o.Context().CheckProxy(o); err != nil {
25962587 return err
25972588 }
2598- if err := o.Context().SendRequest(o, 3); err != nil {
2589+ if err := o.Context().SendDestructor(o, 3); err != nil {
25992590 return err
26002591 }
2601- o.Context().Unregister(o)
26022592 return nil
26032593 }
26042594
@@ -2730,10 +2720,9 @@ func (o *Pointer) Release() error {
27302720 if err := o.Context().CheckProxy(o); err != nil {
27312721 return err
27322722 }
2733- if err := o.Context().SendRequest(o, 1); err != nil {
2723+ if err := o.Context().SendDestructor(o, 1); err != nil {
27342724 return err
27352725 }
2736- o.Context().Unregister(o)
27372726 return nil
27382727 }
27392728
@@ -3169,10 +3158,9 @@ func (o *Keyboard) Release() error {
31693158 if err := o.Context().CheckProxy(o); err != nil {
31703159 return err
31713160 }
3172- if err := o.Context().SendRequest(o, 0); err != nil {
3161+ if err := o.Context().SendDestructor(o, 0); err != nil {
31733162 return err
31743163 }
3175- o.Context().Unregister(o)
31763164 return nil
31773165 }
31783166
@@ -3425,10 +3413,9 @@ func (o *Touch) Release() error {
34253413 if err := o.Context().CheckProxy(o); err != nil {
34263414 return err
34273415 }
3428- if err := o.Context().SendRequest(o, 0); err != nil {
3416+ if err := o.Context().SendDestructor(o, 0); err != nil {
34293417 return err
34303418 }
3431- o.Context().Unregister(o)
34323419 return nil
34333420 }
34343421
@@ -3705,10 +3692,9 @@ func (o *Output) Release() error {
37053692 if err := o.Context().CheckProxy(o); err != nil {
37063693 return err
37073694 }
3708- if err := o.Context().SendRequest(o, 0); err != nil {
3695+ if err := o.Context().SendDestructor(o, 0); err != nil {
37093696 return err
37103697 }
3711- o.Context().Unregister(o)
37123698 return nil
37133699 }
37143700
@@ -3943,10 +3929,9 @@ func (o *Region) Destroy() error {
39433929 if err := o.Context().CheckProxy(o); err != nil {
39443930 return err
39453931 }
3946- if err := o.Context().SendRequest(o, 0); err != nil {
3932+ if err := o.Context().SendDestructor(o, 0); err != nil {
39473933 return err
39483934 }
3949- o.Context().Unregister(o)
39503935 return nil
39513936 }
39523937
@@ -4008,10 +3993,9 @@ func (o *Subcompositor) Destroy() error {
40083993 if err := o.Context().CheckProxy(o); err != nil {
40093994 return err
40103995 }
4011- if err := o.Context().SendRequest(o, 0); err != nil {
3996+ if err := o.Context().SendDestructor(o, 0); err != nil {
40123997 return err
40133998 }
4014- o.Context().Unregister(o)
40153999 return nil
40164000 }
40174001
@@ -4075,10 +4059,9 @@ func (o *Subsurface) Destroy() error {
40754059 if err := o.Context().CheckProxy(o); err != nil {
40764060 return err
40774061 }
4078- if err := o.Context().SendRequest(o, 0); err != nil {
4062+ if err := o.Context().SendDestructor(o, 0); err != nil {
40794063 return err
40804064 }
4081- o.Context().Unregister(o)
40824065 return nil
40834066 }
40844067
@@ -4181,10 +4164,9 @@ func (o *Fixes) Destroy() error {
41814164 if err := o.Context().CheckProxy(o); err != nil {
41824165 return err
41834166 }
4184- if err := o.Context().SendRequest(o, 0); err != nil {
4167+ if err := o.Context().SendDestructor(o, 0); err != nil {
41854168 return err
41864169 }
4187- o.Context().Unregister(o)
41884170 return nil
41894171 }
41904172
@@ -808,10 +808,9 @@ func (o *Compositor) Release() error {
808 if err := o.Context().CheckProxy(o); err != nil {808 if err := o.Context().CheckProxy(o); err != nil {
809 return err809 return err
810 }810 }
811- if err := o.Context().SendRequest(o, 2); err != nil {811+ if err := o.Context().SendDestructor(o, 2); err != nil {
812 return err812 return err
813 }813 }
814- o.Context().Unregister(o)
815 return nil814 return nil
816 }815 }
817 816
@@ -867,10 +866,9 @@ func (o *ShmPool) Destroy() error {
867 if err := o.Context().CheckProxy(o); err != nil {866 if err := o.Context().CheckProxy(o); err != nil {
868 return err867 return err
869 }868 }
870- if err := o.Context().SendRequest(o, 1); err != nil {869+ if err := o.Context().SendDestructor(o, 1); err != nil {
871 return err870 return err
872 }871 }
873- o.Context().Unregister(o)
874 return nil872 return nil
875 }873 }
876 874
@@ -942,10 +940,9 @@ func (o *Shm) Release() error {
942 if err := o.Context().CheckProxy(o); err != nil {940 if err := o.Context().CheckProxy(o); err != nil {
943 return err941 return err
944 }942 }
945- if err := o.Context().SendRequest(o, 1); err != nil {943+ if err := o.Context().SendDestructor(o, 1); err != nil {
946 return err944 return err
947 }945 }
948- o.Context().Unregister(o)
949 return nil946 return nil
950 }947 }
951 948
@@ -1020,10 +1017,9 @@ func (o *Buffer) Destroy() error {
1020 if err := o.Context().CheckProxy(o); err != nil {1017 if err := o.Context().CheckProxy(o); err != nil {
1021 return err1018 return err
1022 }1019 }
1023- if err := o.Context().SendRequest(o, 0); err != nil {1020+ if err := o.Context().SendDestructor(o, 0); err != nil {
1024 return err1021 return err
1025 }1022 }
1026- o.Context().Unregister(o)
1027 return nil1023 return nil
1028 }1024 }
1029 1025
@@ -1122,10 +1118,9 @@ func (o *DataOffer) Destroy() error {
1122 if err := o.Context().CheckProxy(o); err != nil {1118 if err := o.Context().CheckProxy(o); err != nil {
1123 return err1119 return err
1124 }1120 }
1125- if err := o.Context().SendRequest(o, 2); err != nil {1121+ if err := o.Context().SendDestructor(o, 2); err != nil {
1126 return err1122 return err
1127 }1123 }
1128- o.Context().Unregister(o)
1129 return nil1124 return nil
1130 }1125 }
1131 1126
@@ -1300,10 +1295,9 @@ func (o *DataSource) Destroy() error {
1300 if err := o.Context().CheckProxy(o); err != nil {1295 if err := o.Context().CheckProxy(o); err != nil {
1301 return err1296 return err
1302 }1297 }
1303- if err := o.Context().SendRequest(o, 1); err != nil {1298+ if err := o.Context().SendDestructor(o, 1); err != nil {
1304 return err1299 return err
1305 }1300 }
1306- o.Context().Unregister(o)
1307 return nil1301 return nil
1308 }1302 }
1309 1303
@@ -1589,10 +1583,9 @@ func (o *DataDevice) Release() error {
1589 if err := o.Context().CheckProxy(o); err != nil {1583 if err := o.Context().CheckProxy(o); err != nil {
1590 return err1584 return err
1591 }1585 }
1592- if err := o.Context().SendRequest(o, 2); err != nil {1586+ if err := o.Context().SendDestructor(o, 2); err != nil {
1593 return err1587 return err
1594 }1588 }
1595- o.Context().Unregister(o)
1596 return nil1589 return nil
1597 }1590 }
1598 1591
@@ -1862,10 +1855,9 @@ func (o *DataDeviceManager) Release() error {
1862 if err := o.Context().CheckProxy(o); err != nil {1855 if err := o.Context().CheckProxy(o); err != nil {
1863 return err1856 return err
1864 }1857 }
1865- if err := o.Context().SendRequest(o, 2); err != nil {1858+ if err := o.Context().SendDestructor(o, 2); err != nil {
1866 return err1859 return err
1867 }1860 }
1868- o.Context().Unregister(o)
1869 return nil1861 return nil
1870 }1862 }
1871 1863
@@ -2232,10 +2224,9 @@ func (o *Surface) Destroy() error {
2232 if err := o.Context().CheckProxy(o); err != nil {2224 if err := o.Context().CheckProxy(o); err != nil {
2233 return err2225 return err
2234 }2226 }
2235- if err := o.Context().SendRequest(o, 0); err != nil {2227+ if err := o.Context().SendDestructor(o, 0); err != nil {
2236 return err2228 return err
2237 }2229 }
2238- o.Context().Unregister(o)
2239 return nil2230 return nil
2240 }2231 }
2241 2232
@@ -2595,10 +2586,9 @@ func (o *Seat) Release() error {
2595 if err := o.Context().CheckProxy(o); err != nil {2586 if err := o.Context().CheckProxy(o); err != nil {
2596 return err2587 return err
2597 }2588 }
2598- if err := o.Context().SendRequest(o, 3); err != nil {2589+ if err := o.Context().SendDestructor(o, 3); err != nil {
2599 return err2590 return err
2600 }2591 }
2601- o.Context().Unregister(o)
2602 return nil2592 return nil
2603 }2593 }
2604 2594
@@ -2730,10 +2720,9 @@ func (o *Pointer) Release() error {
2730 if err := o.Context().CheckProxy(o); err != nil {2720 if err := o.Context().CheckProxy(o); err != nil {
2731 return err2721 return err
2732 }2722 }
2733- if err := o.Context().SendRequest(o, 1); err != nil {2723+ if err := o.Context().SendDestructor(o, 1); err != nil {
2734 return err2724 return err
2735 }2725 }
2736- o.Context().Unregister(o)
2737 return nil2726 return nil
2738 }2727 }
2739 2728
@@ -3169,10 +3158,9 @@ func (o *Keyboard) Release() error {
3169 if err := o.Context().CheckProxy(o); err != nil {3158 if err := o.Context().CheckProxy(o); err != nil {
3170 return err3159 return err
3171 }3160 }
3172- if err := o.Context().SendRequest(o, 0); err != nil {3161+ if err := o.Context().SendDestructor(o, 0); err != nil {
3173 return err3162 return err
3174 }3163 }
3175- o.Context().Unregister(o)
3176 return nil3164 return nil
3177 }3165 }
3178 3166
@@ -3425,10 +3413,9 @@ func (o *Touch) Release() error {
3425 if err := o.Context().CheckProxy(o); err != nil {3413 if err := o.Context().CheckProxy(o); err != nil {
3426 return err3414 return err
3427 }3415 }
3428- if err := o.Context().SendRequest(o, 0); err != nil {3416+ if err := o.Context().SendDestructor(o, 0); err != nil {
3429 return err3417 return err
3430 }3418 }
3431- o.Context().Unregister(o)
3432 return nil3419 return nil
3433 }3420 }
3434 3421
@@ -3705,10 +3692,9 @@ func (o *Output) Release() error {
3705 if err := o.Context().CheckProxy(o); err != nil {3692 if err := o.Context().CheckProxy(o); err != nil {
3706 return err3693 return err
3707 }3694 }
3708- if err := o.Context().SendRequest(o, 0); err != nil {3695+ if err := o.Context().SendDestructor(o, 0); err != nil {
3709 return err3696 return err
3710 }3697 }
3711- o.Context().Unregister(o)
3712 return nil3698 return nil
3713 }3699 }
3714 3700
@@ -3943,10 +3929,9 @@ func (o *Region) Destroy() error {
3943 if err := o.Context().CheckProxy(o); err != nil {3929 if err := o.Context().CheckProxy(o); err != nil {
3944 return err3930 return err
3945 }3931 }
3946- if err := o.Context().SendRequest(o, 0); err != nil {3932+ if err := o.Context().SendDestructor(o, 0); err != nil {
3947 return err3933 return err
3948 }3934 }
3949- o.Context().Unregister(o)
3950 return nil3935 return nil
3951 }3936 }
3952 3937
@@ -4008,10 +3993,9 @@ func (o *Subcompositor) Destroy() error {
4008 if err := o.Context().CheckProxy(o); err != nil {3993 if err := o.Context().CheckProxy(o); err != nil {
4009 return err3994 return err
4010 }3995 }
4011- if err := o.Context().SendRequest(o, 0); err != nil {3996+ if err := o.Context().SendDestructor(o, 0); err != nil {
4012 return err3997 return err
4013 }3998 }
4014- o.Context().Unregister(o)
4015 return nil3999 return nil
4016 }4000 }
4017 4001
@@ -4075,10 +4059,9 @@ func (o *Subsurface) Destroy() error {
4075 if err := o.Context().CheckProxy(o); err != nil {4059 if err := o.Context().CheckProxy(o); err != nil {
4076 return err4060 return err
4077 }4061 }
4078- if err := o.Context().SendRequest(o, 0); err != nil {4062+ if err := o.Context().SendDestructor(o, 0); err != nil {
4079 return err4063 return err
4080 }4064 }
4081- o.Context().Unregister(o)
4082 return nil4065 return nil
4083 }4066 }
4084 4067
@@ -4181,10 +4164,9 @@ func (o *Fixes) Destroy() error {
4181 if err := o.Context().CheckProxy(o); err != nil {4164 if err := o.Context().CheckProxy(o); err != nil {
4182 return err4165 return err
4183 }4166 }
4184- if err := o.Context().SendRequest(o, 0); err != nil {4167+ if err := o.Context().SendDestructor(o, 0); err != nil {
4185 return err4168 return err
4186 }4169 }
4187- o.Context().Unregister(o)
4188 return nil4170 return nil
4189 }4171 }
4190 4172

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close