Skip to content

Commit b0deec7

Browse files
authored
Merge pull request #146 from lpabon/ctrl-c-signal
Catch CTRL-C and cleanup
2 parents e585c5f + 73f63c1 commit b0deec7

File tree

3 files changed

+145
-9
lines changed

3 files changed

+145
-9
lines changed

cmd/root.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616
package cmd
1717

1818
import (
19+
"os"
20+
1921
"github.com/portworx/pxc/pkg/commander"
2022
"github.com/portworx/pxc/pkg/config"
2123
"github.com/portworx/pxc/pkg/kubernetes"
@@ -31,8 +33,9 @@ type rootFlags struct {
3133

3234
// rootCmd represents the base command when called without any subcommands
3335
var (
34-
rootCmd *cobra.Command
35-
rootOptions *rootFlags
36+
rootCmd *cobra.Command
37+
rootOptions *rootFlags
38+
rootSignalHandler *util.SigIntManager
3639

3740
// This template allow pxc to override the way it prints out the help. This template
3841
// allows pxc to not print out global options unless requested.
@@ -83,6 +86,7 @@ Please see https://docs.portworx.com/reference/ for more information.`,
8386
PersistentPostRunE: rootPersistentPostRunE,
8487
RunE: rootCmdExec,
8588
}
89+
8690
})
8791

8892
var _ = commander.RegisterCommandInit(func() {
@@ -125,6 +129,14 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) error {
125129
// Set version
126130
logrus.Infof("pxc version: %s", PxVersion)
127131

132+
// Capture any CTRL-C
133+
rootSignalHandler = util.NewSigIntManager(func() {
134+
cleanup()
135+
util.Printf("\n*** Interrupt\n")
136+
os.Exit(1)
137+
})
138+
rootSignalHandler.Start()
139+
128140
// The following commands do not need to load configuration
129141
switch cmd.Name() {
130142
case "version":
@@ -141,7 +153,12 @@ func rootPersistentPreRunE(cmd *cobra.Command, args []string) error {
141153

142154
func rootPersistentPostRunE(cmd *cobra.Command, args []string) error {
143155
// Close the global tunnel if any
144-
kubernetes.StopTunnel()
156+
cleanup()
157+
rootSignalHandler.Stop()
145158

146159
return nil
147160
}
161+
162+
func cleanup() {
163+
kubernetes.StopTunnel()
164+
}

pkg/kubernetes/portforward.go

+38-6
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import (
1919
"fmt"
2020
"os/exec"
2121
"strings"
22+
"sync"
2223

2324
"github.com/portworx/pxc/pkg/config"
25+
"github.com/portworx/pxc/pkg/util"
2426

2527
"github.com/sirupsen/logrus"
2628
)
@@ -34,9 +36,12 @@ type PortForwarder interface {
3436

3537
// KubectlPortForwarder object
3638
type KubectlPortForwarder struct {
37-
kubeconfig string
38-
endpoint string
39-
cmd *exec.Cmd
39+
kubeconfig string
40+
endpoint string
41+
cmd *exec.Cmd
42+
signhandler *util.SigIntManager
43+
lock sync.Mutex
44+
running bool
4045
}
4146

4247
var (
@@ -80,6 +85,13 @@ func newKubectlPortForwarder(kubeconfig string) *KubectlPortForwarder {
8085

8186
// Start creates the portforward using kubectl
8287
func (p *KubectlPortForwarder) Start() error {
88+
p.lock.Lock()
89+
defer p.lock.Unlock()
90+
91+
if p.running {
92+
return fmt.Errorf("Tunnel already running")
93+
}
94+
8395
args := config.KM().KubectlFlagsToCliArgs()
8496
currentCluster := config.CM().GetCurrentCluster()
8597
logrus.Debugf("port-forward: CurrentCluster: %v", *currentCluster)
@@ -98,6 +110,11 @@ func (p *KubectlPortForwarder) Start() error {
98110
return fmt.Errorf("Unable to setup kubectl: %v", err)
99111
}
100112

113+
p.signhandler = util.NewSigIntManager(func() {
114+
p.Stop()
115+
})
116+
p.signhandler.Start()
117+
101118
// Start the port forward process
102119
err = cmd.Start()
103120
if err != nil {
@@ -129,15 +146,31 @@ func (p *KubectlPortForwarder) Start() error {
129146
logrus.Debugf("Read %d bytes", n)
130147
logrus.Debugf("Output: %s", sbuf)
131148

149+
p.running = true
132150
return nil
133151
}
134152

135153
// Stop ends the session
136154
func (p *KubectlPortForwarder) Stop() error {
137-
logrus.Debug("Port forwarding stopped")
155+
p.lock.Lock()
156+
defer p.lock.Unlock()
157+
158+
if !p.running {
159+
return nil
160+
}
161+
138162
if p.cmd != nil {
139-
return p.cmd.Process.Kill()
163+
logrus.Debug("Port forwarding stopped")
164+
err := p.cmd.Process.Kill()
165+
p.cmd = nil
166+
return err
167+
}
168+
169+
if p.signhandler != nil {
170+
p.signhandler.Stop()
171+
p.signhandler = nil
140172
}
173+
p.running = false
141174
return nil
142175
}
143176

@@ -149,7 +182,6 @@ func (p *KubectlPortForwarder) Endpoint() string {
149182
func (p *KubectlPortForwarder) getEndpointFromKubectlOutput(sbuf string) (string, error) {
150183
index := strings.Index(sbuf, "127.0.0.1:")
151184
if index >= 0 {
152-
//return strings.Split(sbuf[index:], " ")[0], nil
153185
e := strings.Split(sbuf[index:], " ")[0]
154186
e = "localhost:" + strings.Split(e, ":")[1]
155187
return e, nil

pkg/util/sigint.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright © 2020 Portworx
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package util
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"os/signal"
23+
"sync"
24+
"syscall"
25+
26+
"github.com/sirupsen/logrus"
27+
)
28+
29+
type SigIntManager struct {
30+
handler func()
31+
done chan bool
32+
wg sync.WaitGroup
33+
lock sync.Mutex
34+
running bool
35+
}
36+
37+
func NewSigIntManager(handler func()) *SigIntManager {
38+
return &SigIntManager{
39+
handler: handler,
40+
done: make(chan bool, 1),
41+
}
42+
}
43+
44+
func (s *SigIntManager) Start() error {
45+
s.lock.Lock()
46+
defer s.lock.Unlock()
47+
48+
if s.running {
49+
return fmt.Errorf("already running a signal handler")
50+
}
51+
52+
var startwg sync.WaitGroup
53+
signalch := make(chan os.Signal, 1)
54+
signal.Notify(signalch, os.Interrupt, os.Kill, syscall.SIGINT, syscall.SIGTERM)
55+
startwg.Add(1)
56+
s.wg.Add(1)
57+
go func() {
58+
startwg.Done()
59+
select {
60+
case <-signalch:
61+
logrus.Info("Ctrl-C captured...")
62+
s.handler()
63+
case <-s.done:
64+
logrus.Debug("Closing Ctrl-C capturing function")
65+
}
66+
s.wg.Done()
67+
}()
68+
startwg.Wait()
69+
70+
s.running = true
71+
return nil
72+
}
73+
74+
func (s *SigIntManager) Stop() error {
75+
s.lock.Lock()
76+
defer s.lock.Unlock()
77+
78+
if !s.running {
79+
return nil
80+
}
81+
82+
s.done <- true
83+
s.wg.Wait()
84+
s.running = false
85+
86+
return nil
87+
}

0 commit comments

Comments
 (0)