GODT-1523: Reduce unnecessary shell executions. Inspired by @kortschak.

This commit is contained in:
Jakub
2022-08-16 15:53:29 +02:00
parent 1ed7b690a5
commit 8ebdb466f7
15 changed files with 240 additions and 117 deletions

View File

@ -180,6 +180,7 @@ build-linux-qa:
- export PATH=$GOPATH/bin:$PATH - export PATH=$GOPATH/bin:$PATH
- export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header' - export CGO_CPPFLAGS='-Wno-error -Wno-nullability-completeness -Wno-expansion-to-defined -Wno-builtin-requires-header'
script: script:
- go version
- make build - make build
- git diff && git diff-index --quiet HEAD - git diff && git diff-index --quiet HEAD
cache: {} cache: {}

View File

@ -98,7 +98,7 @@ func main() { //nolint:funlen
logrus.WithError(err).Fatal("Failed to determine path to launcher") logrus.WithError(err).Fatal("Failed to determine path to launcher")
} }
cmd := execabs.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) // nolint:gosec cmd := execabs.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) //nolint:gosec
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout

2
go.mod
View File

@ -73,7 +73,7 @@ require (
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 golang.org/x/sys v0.0.0-20220111092808-5a964db01320
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0
) )
replace ( replace (

View File

@ -22,7 +22,6 @@ import (
"strings" "strings"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"golang.org/x/sys/execabs"
) )
// IsCatalinaOrNewer checks whether the host is MacOS Catalina 10.15.x or higher. // IsCatalinaOrNewer checks whether the host is MacOS Catalina 10.15.x or higher.
@ -35,20 +34,20 @@ func IsBigSurOrNewer() bool {
return isThisDarwinNewerOrEqual(getMinBigSur()) return isThisDarwinNewerOrEqual(getMinBigSur())
} }
func getMinCatalina() *semver.Version { return semver.MustParse("10.15.0") } func getMinCatalina() *semver.Version { return semver.MustParse("19.0.0") }
func getMinBigSur() *semver.Version { return semver.MustParse("10.16.0") } func getMinBigSur() *semver.Version { return semver.MustParse("20.0.0") }
func isThisDarwinNewerOrEqual(minVersion *semver.Version) bool { func isThisDarwinNewerOrEqual(minVersion *semver.Version) bool {
if runtime.GOOS != "darwin" { if runtime.GOOS != "darwin" {
return false return false
} }
rawVersion, err := execabs.Command("sw_vers", "-productVersion").Output() rawVersion, err := getDarwinVersion()
if err != nil { if err != nil {
return false return false
} }
return isVersionEqualOrNewer(minVersion, strings.TrimSpace(string(rawVersion))) return isVersionEqualOrNewer(minVersion, strings.TrimSpace(rawVersion))
} }
// isVersionEqualOrNewer is separated to be able to run test on other than darwin. // isVersionEqualOrNewer is separated to be able to run test on other than darwin.

View File

@ -0,0 +1,29 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build darwin
// +build darwin
package useragent
import (
"syscall"
)
func getDarwinVersion() (string, error) {
return syscall.Sysctl("kern.osrelease")
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build !darwin
// +build !darwin
package useragent
import "errors"
func getDarwinVersion() (string, error) {
return "", errors.New("implemented only for darwin")
}

View File

@ -25,16 +25,11 @@ import (
func TestIsVersionCatalinaOrNewer(t *testing.T) { func TestIsVersionCatalinaOrNewer(t *testing.T) {
testData := map[struct{ version string }]bool{ testData := map[struct{ version string }]bool{
{""}: false, {""}: false,
{"9.0.0"}: false, {"18.0.0"}: false,
{"9.15.0"}: false, {"19.0.0"}: true,
{"10.13.0"}: false, {"20.0.0"}: true,
{"10.14.0"}: false, {"21.0.0"}: true,
{"10.14.99"}: false,
{"10.15.0"}: true,
{"10.16.0"}: true,
{"11.0.0"}: true,
{"11.1"}: true,
} }
for args, exp := range testData { for args, exp := range testData {
@ -45,16 +40,11 @@ func TestIsVersionCatalinaOrNewer(t *testing.T) {
func TestIsVersionBigSurOrNewer(t *testing.T) { func TestIsVersionBigSurOrNewer(t *testing.T) {
testData := map[struct{ version string }]bool{ testData := map[struct{ version string }]bool{
{""}: false, {""}: false,
{"9.0.0"}: false, {"18.0.0"}: false,
{"9.15.0"}: false, {"19.0.0"}: false,
{"10.13.0"}: false, {"20.0.0"}: true,
{"10.14.0"}: false, {"21.0.0"}: true,
{"10.14.99"}: false,
{"10.15.0"}: false,
{"10.16.0"}: true,
{"11.0.0"}: true,
{"11.1"}: true,
} }
for args, exp := range testData { for args, exp := range testData {

View File

@ -28,10 +28,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/v2/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/pkg/mobileconfig" "github.com/ProtonMail/proton-bridge/v2/pkg/mobileconfig"
"golang.org/x/sys/execabs" "golang.org/x/sys/execabs"
) )

View File

@ -21,15 +21,34 @@
package theme package theme
import ( import (
"strings" "os"
"path/filepath"
"golang.org/x/sys/execabs" "howett.net/plist"
) )
func detectSystemTheme() Theme { func detectSystemTheme() Theme {
out, err := execabs.Command("defaults", "read", "-g", "AppleInterfaceStyle").Output() //nolint:gosec home, err := os.UserHomeDir()
if err == nil && strings.TrimSpace(string(out)) == "Dark" { if err != nil {
return Light
}
path := filepath.Join(home, "/Library/Preferences/.GlobalPreferences.plist")
prefFile, err := os.Open(path)
if err != nil {
return Light
}
defer prefFile.Close()
var data struct {
AppleInterfaceStyle string `plist:AppleInterfaceStyle`
}
dec := plist.NewDecoder(prefFile)
err = dec.Decode(&data)
if err == nil && data.AppleInterfaceStyle == "Dark" {
return Dark return Dark
} }
return Light return Light
} }

View File

@ -313,16 +313,16 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
startTime := time.Now() startTime := time.Now()
for isSending && time.Since(startTime) < 90*time.Second { for isSending && time.Since(startTime) < 90*time.Second {
log.Debug("Message is still in send queue, waiting for a bit") log.Warn("Message is still in send queue, waiting for a bit")
time.Sleep(15 * time.Second) time.Sleep(15 * time.Second)
isSending, wasSent = su.backend.sendRecorder.isSendingOrSent(su.client(), sendRecorderMessageHash) isSending, wasSent = su.backend.sendRecorder.isSendingOrSent(su.client(), sendRecorderMessageHash)
} }
if isSending { if isSending {
log.Debug("Message is still in send queue, returning error to prevent client from adding it to the sent folder prematurely") log.Warn("Message is still in send queue, returning error to prevent client from adding it to the sent folder prematurely")
return errors.New("original message is still being sent") return errors.New("original message is still being sent")
} }
if wasSent { if wasSent {
log.Debug("Message was already sent") log.Warn("Message was already sent")
return nil return nil
} }

View File

@ -15,46 +15,55 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build !windows
// +build !windows
package store package store
import ( import (
"os"
"runtime" "runtime"
"syscall"
"golang.org/x/sys/unix"
) )
func getCurrentFDLimit() (int, error) {
var limits syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
if err != nil {
return 0, err
}
return int(limits.Cur), nil
}
func countOpenedFDs(limit int) int {
openedFDs := 0
for i := 0; i < limit; i++ {
_, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(i), uintptr(syscall.F_GETFL), 0)
if err == 0 {
openedFDs++
}
}
return openedFDs
}
func isFdCloseToULimit() bool { func isFdCloseToULimit() bool {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
return false return false
} }
var fdPath string limit, err := getCurrentFDLimit()
switch runtime.GOOS {
case "darwin":
fdPath = "/dev/fd"
case "linux":
fdPath = "/proc/self/fd"
}
f, err := os.Open(fdPath)
if err != nil { if err != nil {
log.Warn("isFdCloseToULimit: ", err) log.WithError(err).Error("Cannot get current FD limit")
return false return false
} }
d, err := f.ReadDir(-1)
if err != nil {
log.Warn("isFdCloseToULimit: ", err)
return false
}
fd := len(d) - 1
var lim unix.Rlimit openedFDs := countOpenedFDs(limit)
err = unix.Getrlimit(unix.RLIMIT_NOFILE, &lim)
if err != nil {
log.Print(err)
}
ulimit := lim.Max
log.Info("File descriptor check: num goroutines ", runtime.NumGoroutine(), " fd ", fd, " ulimit ", ulimit) log.
return fd >= int(0.95*float64(ulimit)) WithField("noGoroutines", runtime.NumCgoCall()).
WithField("noFDs", openedFDs).
WithField("limitFD", limit).
Info("File descriptor check")
return openedFDs >= int(0.95*float64(limit))
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build windows
// +build windows
package store
func isFdCloseToULimit() bool { return false }

View File

@ -25,7 +25,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/sys/execabs"
) )
const ( const (
@ -39,77 +38,103 @@ const (
func TestSyncFolder(t *testing.T) { func TestSyncFolder(t *testing.T) {
for _, srcType := range []string{EmptyType, FileType, SymlinkType, DirType} { for _, srcType := range []string{EmptyType, FileType, SymlinkType, DirType} {
for _, dstType := range []string{EmptyType, FileType, SymlinkType, DirType} { for _, dstType := range []string{EmptyType, FileType, SymlinkType, DirType} {
require.NoError(t, checkCopyWorks(srcType, dstType)) checkCopyWorks(t, srcType, dstType)
logrus.Warn("OK: from ", srcType, " to ", dstType) logrus.Warn("OK: from ", srcType, " to ", dstType)
} }
} }
} }
func checkCopyWorks(srcType, dstType string) error { func checkCopyWorks(tb testing.TB, srcType, dstType string) {
r := require.New(tb)
dirName := "from_" + srcType + "_to_" + dstType dirName := "from_" + srcType + "_to_" + dstType
AppCacheDir := "/tmp" AppCacheDir := "/tmp"
srcDir := filepath.Join(AppCacheDir, "sync_src", dirName) srcDir := filepath.Join(AppCacheDir, "sync_src", dirName)
destDir := filepath.Join(AppCacheDir, "sync_dst", dirName) destDir := filepath.Join(AppCacheDir, "sync_dst", dirName)
// clear before // clear before
logrus.Info("remove all ", srcDir) r.NoError(os.RemoveAll(srcDir))
err := os.RemoveAll(srcDir) r.NoError(os.RemoveAll(destDir))
if err != nil {
return err
}
logrus.Info("remove all ", destDir)
err = os.RemoveAll(destDir)
if err != nil {
return err
}
// create // create
err = createTestFolder(srcDir, srcType) r.NoError(createTestFolder(srcDir, srcType))
if err != nil { r.NoError(createTestFolder(destDir, dstType))
return err
}
err = createTestFolder(destDir, dstType)
if err != nil {
return err
}
// copy // copy
logrus.Info("Sync from ", srcDir, " to ", destDir) r.NoError(syncFolders(destDir, srcDir))
err = syncFolders(destDir, srcDir)
if err != nil {
return err
}
// Check // Check
logrus.Info("check ", srcDir, " and ", destDir) checkThatFilesAreSame(r, srcDir, destDir)
err = checkThatFilesAreSame(srcDir, destDir)
if err != nil {
return err
}
// clear after // clear after
logrus.Info("remove all ", srcDir) r.NoError(os.RemoveAll(srcDir))
err = os.RemoveAll(srcDir) r.NoError(os.RemoveAll(destDir))
if err != nil {
return err
}
logrus.Info("remove all ", destDir)
err = os.RemoveAll(destDir)
if err != nil {
return err
}
return err
} }
func checkThatFilesAreSame(src, dst string) error { func checkThatFilesAreSame(r *require.Assertions, src, dst string) {
cmd := execabs.Command("diff", "-qr", src, dst) //nolint:gosec srcFiles, srcDirs, err := walkDir(src)
cmd.Stderr = logrus.StandardLogger().WriterLevel(logrus.ErrorLevel) r.NoError(err)
cmd.Stdout = logrus.StandardLogger().WriterLevel(logrus.InfoLevel)
return cmd.Run() dstFiles, dstDirs, err := walkDir(dst)
r.NoError(err)
r.ElementsMatch(srcFiles, dstFiles)
r.ElementsMatch(srcDirs, dstDirs)
for _, relPath := range srcFiles {
srcPath := filepath.Join(src, relPath)
r.FileExists(srcPath)
dstPath := filepath.Join(dst, relPath)
r.FileExists(dstPath)
srcInfo, err := os.Lstat(srcPath)
r.NoError(err)
dstInfo, err := os.Lstat(dstPath)
r.NoError(err)
r.Equal(srcInfo.Mode(), dstInfo.Mode())
if srcInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
srcLnk, err := os.Readlink(srcPath)
r.NoError(err)
dstLnk, err := os.Readlink(dstPath)
r.NoError(err)
r.Equal(srcLnk, dstLnk)
} else {
srcContent, err := ioutil.ReadFile(srcPath)
r.NoError(err)
dstContent, err := ioutil.ReadFile(dstPath)
r.NoError(err)
r.Equal(srcContent, dstContent)
}
}
}
func walkDir(dir string) (files, dirs []string, err error) {
err = filepath.Walk(dir, func(path string, info os.FileInfo, errWalk error) error {
if errWalk != nil {
return errWalk
}
relPath, errRel := filepath.Rel(dir, path)
if errRel != nil {
return errRel
}
if info.IsDir() {
dirs = append(dirs, relPath)
} else {
files = append(files, relPath)
}
return nil
})
return
} }
func createTestFolder(dirPath, dirType string) error { func createTestFolder(dirPath, dirType string) error {

View File

@ -175,5 +175,6 @@ func (ctx *TestContext) MessagePreparationFinished(username string) {
} }
func (ctx *TestContext) CredentialsFailsOnWrite(shouldFail bool) { func (ctx *TestContext) CredentialsFailsOnWrite(shouldFail bool) {
ctx.credStore.(*fakeCredStore).failOnWrite = shouldFail //nolint:forcetypeassert ctx.credStore.(*fakeCredStore).failOnWrite = shouldFail //nolint:forcetypeassert
ctx.addCleanup(func() { ctx.credStore.(*fakeCredStore).failOnWrite = false }, "credentials-cleanup") //nolint:forcetypeassert
} }

View File

@ -35,7 +35,7 @@ type fakeCredStore struct {
// newFakeCredStore returns a fake credentials store (optionally configured with the given credentials). // newFakeCredStore returns a fake credentials store (optionally configured with the given credentials).
func newFakeCredStore(initCreds ...*credentials.Credentials) (c *fakeCredStore) { func newFakeCredStore(initCreds ...*credentials.Credentials) (c *fakeCredStore) {
c = &fakeCredStore{credentials: map[string]*credentials.Credentials{}} c = &fakeCredStore{credentials: map[string]*credentials.Credentials{}, failOnWrite: false}
for _, creds := range initCreds { for _, creds := range initCreds {
if creds == nil { if creds == nil {
continue continue