Browse Source

First version, examples, test object

Vova Tkach 5 years ago
parent
commit
513344e4b9

+ 1 - 1
.gitignore

@@ -9,7 +9,7 @@
 *.test
 
 # Output of the go coverage tool, specifically when used with LiteIDE
-*.out
+out
 
 # Dependency directories (remove the comment below to include it)
 # vendor/

+ 17 - 0
Makefile

@@ -0,0 +1,17 @@
+default: debug test run
+
+debug:
+	go vet ./...
+	gofmt -d ./
+	gofmt -w ./
+	go build -mod vendor -o ./out
+
+test:
+	go test ./...
+
+run:
+	@./out
+
+update:
+	go mod vendor
+	go mod download

+ 26 - 0
ctrlc/colors.go

@@ -0,0 +1,26 @@
+package ctrlc
+
+import (
+	"fmt"
+)
+
+func clr(str string) string {
+	if !IS_WIN_PLATFORM {
+		return fmt.Sprintf("\033[1;31m%s\033[0m", str)
+	}
+	return str
+}
+
+func clg(str string) string {
+	if !IS_WIN_PLATFORM {
+		return fmt.Sprintf("\033[1;32m%s\033[0m", str)
+	}
+	return str
+}
+
+func cly(str string) string {
+	if !IS_WIN_PLATFORM {
+		return fmt.Sprintf("\033[1;33m%s\033[0m", str)
+	}
+	return str
+}

+ 3 - 0
ctrlc/colors_test.go

@@ -0,0 +1,3 @@
+package ctrlc
+
+// Nothing to test here

+ 114 - 0
ctrlc/ctrlc.go

@@ -0,0 +1,114 @@
+package ctrlc
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+)
+
+const C_ICON_START = "🌟"
+const C_ICON_WARN = "⚡️"
+const C_ICON_HOT = "🔥"
+const C_ICON_MAG = "✨"
+const C_ICON_SC = "🌳"
+
+type Iface interface {
+	Shutdown(ctx context.Context) error
+}
+
+func App(t time.Duration, f func(ctx context.Context) *[]Iface) {
+	stop := make(chan os.Signal)
+	signal.Notify(stop, syscall.SIGTERM)
+	signal.Notify(stop, syscall.SIGINT)
+
+	fmt.Printf(
+		C_ICON_START+" %s\n",
+		cly(
+			fmt.Sprintf(
+				"Application started (%d sec)",
+				t/time.Second,
+			),
+		),
+	)
+
+	sctx, shutdown := context.WithCancel(context.Background())
+	ifaces := f(sctx)
+
+	switch val := <-stop; val {
+	case syscall.SIGINT:
+		fmt.Printf(
+			"\r"+C_ICON_WARN+" %s\n",
+			cly(
+				fmt.Sprintf(
+					"Shutting down (interrupt) (%d sec)",
+					t/time.Second,
+				),
+			),
+		)
+	case syscall.SIGTERM:
+		fmt.Printf(
+			C_ICON_WARN+" %s\n",
+			cly(
+				fmt.Sprintf(
+					"Shutting down (terminate) (%d sec)",
+					t/time.Second,
+				),
+			),
+		)
+	default:
+		fmt.Printf(
+			C_ICON_WARN+" %s\n",
+			cly(
+				fmt.Sprintf(
+					"Shutting down (%d sec)",
+					t/time.Second,
+				),
+			),
+		)
+	}
+
+	shutdown()
+
+	errors := false
+	ctx, cancel := context.WithTimeout(context.Background(), t)
+	for _, iface := range *ifaces {
+		if err := iface.Shutdown(ctx); err != nil {
+			errors = true
+			fmt.Printf(
+				C_ICON_HOT+" %s\n",
+				clr(fmt.Sprintf(
+					"Shutdown error (%T): %s",
+					iface,
+					err.Error(),
+				)),
+			)
+		}
+	}
+	cancel()
+
+	if errors {
+		fmt.Printf(
+			C_ICON_MAG+" %s\n",
+			cly(
+				fmt.Sprintf(
+					"Application exited with errors (%d sec)",
+					t/time.Second,
+				),
+			),
+		)
+		os.Exit(1)
+	} else {
+		fmt.Printf(
+			C_ICON_SC+" %s\n",
+			clg(
+				fmt.Sprintf(
+					"Application exited successfully (%d sec)",
+					t/time.Second,
+				),
+			),
+		)
+	}
+}

+ 3 - 0
ctrlc/ctrlc_test.go

@@ -0,0 +1,3 @@
+package ctrlc
+
+// Nothing to test here

+ 5 - 0
ctrlc/platform.go

@@ -0,0 +1,5 @@
+package ctrlc
+
+func IsWinPlatform() bool {
+	return IS_WIN_PLATFORM
+}

+ 5 - 0
ctrlc/platform_other.go

@@ -0,0 +1,5 @@
+// +build !windows
+
+package ctrlc
+
+const IS_WIN_PLATFORM = false

+ 3 - 0
ctrlc/platform_other_test.go

@@ -0,0 +1,3 @@
+package ctrlc
+
+// Nothing to test here

+ 3 - 0
ctrlc/platform_test.go

@@ -0,0 +1,3 @@
+package ctrlc
+
+// Nothing to test here

+ 5 - 0
ctrlc/platform_windows.go

@@ -0,0 +1,5 @@
+// +build windows
+
+package ctrlc
+
+const IS_WIN_PLATFORM = true

+ 3 - 0
ctrlc/platform_windows_test.go

@@ -0,0 +1,3 @@
+package ctrlc
+
+// Nothing to test here

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module github.com/vladimirok5959/golang-ctrlc
+
+go 1.13

+ 57 - 0
main.go

@@ -0,0 +1,57 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/vladimirok5959/golang-ctrlc/ctrlc"
+)
+
+func main() {
+	ctrlc.App(8*time.Second, func(ctx context.Context) *[]ctrlc.Iface {
+		// Some custom logic
+		test := Run()
+
+		// Http web server
+		mux := http.NewServeMux()
+		mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+			fmt.Printf("New web request (%s)!\n", r.URL.Path)
+
+			// Do something hard inside (12 seconds)
+			for i := 0; i < 12000; i++ {
+				select {
+				case <-ctx.Done():
+					// Interrupt request by server
+					fmt.Printf("[BY SERVER] OK, I will cancel (%s)!\n", r.URL.Path)
+					return
+				case <-r.Context().Done():
+					// Interrupt request by client
+					fmt.Printf("[BY CLIENT] OK, I will cancel (%s)!\n", r.URL.Path)
+					return
+				default:
+					// Main some logic
+					time.Sleep(1 * time.Millisecond)
+				}
+			}
+
+			fmt.Printf("After 12 seconds!\n")
+			w.Header().Set("Content-Type", "text/html")
+			w.Write([]byte(`<div>After 12 seconds!</div>`))
+			w.Write([]byte(`<div>` + r.URL.Path + `</div>`))
+		})
+		srv := &http.Server{Addr: "127.0.0.1:8080", Handler: mux}
+		go func() {
+			fmt.Printf("Starting web server: http://127.0.0.1:8080/\n")
+			if err := srv.ListenAndServe(); err != nil {
+				if err != http.ErrServerClosed {
+					fmt.Printf("Web server startup error: %s\n", err.Error())
+					return
+				}
+			}
+		}()
+
+		return &[]ctrlc.Iface{test, srv}
+	})
+}

+ 46 - 0
testobject.go

@@ -0,0 +1,46 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"time"
+)
+
+type TestObject struct {
+	ctx    context.Context
+	cancel context.CancelFunc
+	chDone chan bool
+}
+
+func Run() *TestObject {
+	ctx, cancel := context.WithCancel(context.Background())
+	w := &TestObject{ctx: ctx, cancel: cancel, chDone: make(chan bool)}
+
+	go func() {
+		for {
+			select {
+			case <-w.ctx.Done():
+				w.chDone <- true
+				return
+			case <-time.After(1 * time.Second):
+				fmt.Printf("[TestObject]: I do something every second!\n")
+				fmt.Printf("[TestObject]: Press CTRL + C for shutdown...\n")
+			}
+		}
+	}()
+
+	return w
+}
+
+func (this *TestObject) Shutdown(ctx context.Context) error {
+	fmt.Printf("[TestObject]: OK! I will shutdown!\n")
+
+	this.cancel()
+
+	select {
+	case <-this.chDone:
+		return nil
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+}