Skip to content

Commit ea5120f

Browse files
committed
add linter and flag for enabling resource constraints
1 parent cb08ea4 commit ea5120f

File tree

10 files changed

+161
-58
lines changed

10 files changed

+161
-58
lines changed

.golangci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
linters:
2+
disable-all: true
3+
enable:
4+
- errcheck
5+
- goconst
6+
- gocyclo
7+
- gofmt
8+
- goimports
9+
- gosec
10+
- govet
11+
- ineffassign
12+
- lll
13+
- misspell
14+
- nakedret
15+
- prealloc
16+
- staticcheck
17+
- typecheck
18+
- unconvert
19+
- unparam
20+
- unused
21+
- whitespace
22+
fast: false

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,22 @@ To start the server, run the following command:
7171
```sh
7272
./server
7373
```
74-
The default directory where the code files will be stored is `/tmp/`
75-
A separate directory is created for every language.
76-
To change the directory where the code files will be stored(eventually removed by the garbage collector), use
74+
75+
### Flags
76+
- `--code-dir`
77+
The default directory where the code files will be stored is `/tmp/`
78+
A separate directory is created for every language.
79+
To change the directory where the code files will be stored(eventually removed by the garbage collector), use
7780
```sh
7881
./server --code-dir /path/to/code/dir
7982
```
8083

84+
- `--resource-constraints`
85+
By default, resource constraints are turned off to improve the performance, if you want to enable it, use
86+
```sh
87+
./server --resource-constraints true
88+
```
89+
8190
## API
8291

8392
### Submit Code

cmd/flags.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"remote-code-engine/pkg/config"
6+
7+
"go.uber.org/zap"
8+
)
9+
10+
func ParseFlags() {
11+
flag.StringVar(&config.BaseCodePath, "code-dir", "/tmp/", "Base path to store the code files")
12+
flag.BoolVar(&config.ResourceConstraints, "resource-constraints", false, "Enable resource constraints (default false)")
13+
help := flag.Bool("help", false, "Display help")
14+
15+
flag.Parse()
16+
17+
if *help {
18+
flag.Usage()
19+
return
20+
}
21+
22+
logger.Info("parsed the flags",
23+
zap.String("code-dir", config.BaseCodePath),
24+
zap.Bool("resource-constraints", config.ResourceConstraints),
25+
)
26+
}

cmd/server.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"context"
5-
"flag"
65
"net/http"
76
"os"
87
"remote-code-engine/pkg/config"
@@ -55,10 +54,11 @@ func setupCodeDirectory(imageConfig config.ImageConfig) {
5554
}
5655

5756
func main() {
58-
defer logger.Sync()
57+
defer func() {
58+
_ = logger.Sync()
59+
}()
5960

60-
flag.StringVar(&config.BaseCodePath, "code-dir", "/tmp/", "Base path to store the code files")
61-
flag.Parse()
61+
ParseFlags()
6262

6363
// This should be given as a command line argument.
6464
imageConfig, err := config.LoadConfig("../config.yml")
@@ -83,7 +83,23 @@ func main() {
8383
panic(err)
8484
}
8585

86-
go cli.FreeUpZombieContainers(context.Background())
86+
ctx, cancel := context.WithCancel(context.Background())
8787

88-
StartServer(cli, imageConfig)
88+
go func() {
89+
err = cli.FreeUpZombieContainers(ctx)
90+
if err != nil {
91+
logger.Error("failed to free up zombie containers",
92+
zap.Error(err),
93+
)
94+
}
95+
}()
96+
97+
err = StartServer(cli, imageConfig)
98+
if err != nil {
99+
logger.Error("failed to start the server",
100+
zap.Error(err),
101+
)
102+
cancel()
103+
panic(err)
104+
}
89105
}

pkg/config/config.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const (
1717
)
1818

1919
var (
20-
BaseCodePath string
20+
BaseCodePath string
21+
ResourceConstraints bool
2122
)
2223

2324
type LanguageConfig struct {
@@ -55,3 +56,7 @@ func (c *ImageConfig) IsLanguageSupported(lang Language) bool {
5556
func GetHostLanguageCodePath(lang Language) string {
5657
return filepath.Join(BaseCodePath, string(lang))
5758
}
59+
60+
func IsResourceConstraintsEnabled() bool {
61+
return ResourceConstraints
62+
}

pkg/config/config_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestLoadConfig(t *testing.T) {
3434

3535
// Write the sample config to a temporary file
3636
configPath := filepath.Join(tempDir, "config.yaml")
37-
err = os.WriteFile(configPath, data, 0644)
37+
err = os.WriteFile(configPath, data, 0600)
3838
if err != nil {
3939
t.Fatalf("failed to write sample config to file: %v", err)
4040
}

pkg/container/consts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "time"
55
const (
66
MAX_EXECUTION_TIME = 60 * time.Second
77

8-
// The server running this will check for every 10 minutes whether there are zombie containers - completed containers and removes them.
8+
// The server running this will check for every 10 minutes whether there are zombie containers.
99
GarbageCollectionTimeWindow = 5 * time.Minute
1010

1111
// Path where the code files are mounted.

pkg/container/dockerclient.go

Lines changed: 65 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func NewDockerClient(opts *client.Opt, logger *zap.Logger) (ContainerClient, err
4747
}
4848

4949
func (d *dockerClient) GetContainers(ctx context.Context, opts *container.ListOptions) ([]Container, error) {
50-
var containersList []Container
50+
containersList := []Container{}
5151

5252
containers, err := d.client.ContainerList(ctx, *opts)
5353
if err != nil {
@@ -66,6 +66,39 @@ func (d *dockerClient) GetContainers(ctx context.Context, opts *container.ListOp
6666
return containersList, nil
6767
}
6868

69+
func (d *dockerClient) getResourceConstraints() container.Resources {
70+
if config.IsResourceConstraintsEnabled() {
71+
return container.Resources{
72+
Memory: 500 * 1024 * 1024, // 500 MB
73+
NanoCPUs: 1000000000, // 1 CPU
74+
Ulimits: []*units.Ulimit{
75+
{
76+
Name: "nproc",
77+
Soft: 64,
78+
Hard: 128,
79+
},
80+
{
81+
Name: "nofile",
82+
Soft: 64,
83+
Hard: 128,
84+
},
85+
{
86+
Name: "core",
87+
Soft: 0,
88+
Hard: 0,
89+
},
90+
{
91+
// Maximum file size that can be created by the process (output file in our case)
92+
Name: "fsize",
93+
Soft: 20 * 1024 * 1024,
94+
Hard: 20 * 1024 * 1024,
95+
},
96+
},
97+
}
98+
}
99+
return container.Resources{}
100+
}
101+
69102
func (d *dockerClient) ExecuteCode(ctx context.Context, code *Code) (string, error) {
70103
codeFileName, inputFileName, err := createCodeAndInputFilesHost(code, d.logger)
71104
if err != nil {
@@ -76,6 +109,8 @@ func (d *dockerClient) ExecuteCode(ctx context.Context, code *Code) (string, err
76109
zap.String("input file name", inputFileName),
77110
)
78111

112+
resourceConstraints := d.getResourceConstraints()
113+
79114
res, err := d.client.ContainerCreate(ctx, &container.Config{
80115
Cmd: getContainerCommand(code, codeFileName, inputFileName),
81116
Image: code.Image,
@@ -92,40 +127,13 @@ func (d *dockerClient) ExecuteCode(ctx context.Context, code *Code) (string, err
92127
RestartPolicy: container.RestartPolicy{
93128
Name: "no",
94129
},
95-
// We are reading the container logs to get the output. So it's better to disable and have a separate thread to delete stale containers
130+
// We are reading the container logs to get the output.
131+
// So it's better to disable and have a separate thread to delete stale containers
96132
AutoRemove: false,
97133
// Drop all the capabilities
98134
CapDrop: []string{"ALL"},
99135
Privileged: false,
100-
// Set the memory limit to 1GB
101-
Resources: container.Resources{
102-
// set 500 MB as the memory limit in bytes
103-
Memory: 500 * 1024 * 1024,
104-
NanoCPUs: 500000000, // 0.5 CPU
105-
Ulimits: []*units.Ulimit{
106-
{
107-
Name: "nproc",
108-
Soft: 64,
109-
Hard: 128,
110-
},
111-
{
112-
Name: "nofile",
113-
Soft: 64,
114-
Hard: 128,
115-
},
116-
{
117-
Name: "core",
118-
Soft: 0,
119-
Hard: 0,
120-
},
121-
{
122-
// Maximum file size that can be created by the process (output file in our case)
123-
Name: "fsize",
124-
Soft: 20 * 1024 * 1024,
125-
Hard: 20 * 1024 * 1024,
126-
},
127-
},
128-
},
136+
Resources: resourceConstraints,
129137
}, nil, nil, getContainerName())
130138

131139
if err != nil {
@@ -217,22 +225,36 @@ func deleteStaleFiles(dir string, logger *zap.Logger) error {
217225
}
218226

219227
func (d *dockerClient) FreeUpZombieContainers(ctx context.Context) error {
228+
ticker := time.NewTicker(GarbageCollectionTimeWindow)
220229
for {
221-
pruneResults, err := d.client.ContainersPrune(ctx, filters.Args{})
222-
if err != nil {
223-
d.logger.Error("failed to prune containers",
224-
zap.Error(err),
225-
)
226-
}
230+
select {
231+
case <-ctx.Done():
232+
d.logger.Info("stopping the zombie container cleanup routine")
233+
return nil
234+
case <-ticker.C:
235+
pruneResults, err := d.client.ContainersPrune(ctx, filters.Args{})
236+
if err != nil {
237+
d.logger.Error("failed to prune containers",
238+
zap.Error(err),
239+
)
240+
}
227241

228-
d.logger.Info("successfully pruned the containers:",
229-
zap.Int("#Pruned containers", len(pruneResults.ContainersDeleted)),
230-
)
242+
d.logger.Info("successfully pruned the containers:",
243+
zap.Int("#Pruned containers", len(pruneResults.ContainersDeleted)),
244+
)
231245

232-
deleteStaleFiles(config.GetHostLanguageCodePath(config.Cpp), d.logger)
233-
deleteStaleFiles(config.GetHostLanguageCodePath(config.Golang), d.logger)
246+
if err = deleteStaleFiles(config.GetHostLanguageCodePath(config.Cpp), d.logger); err != nil {
247+
d.logger.Error("failed to delete stale files",
248+
zap.Error(err),
249+
)
250+
}
234251

235-
time.Sleep(GarbageCollectionTimeWindow)
252+
if err = deleteStaleFiles(config.GetHostLanguageCodePath(config.Golang), d.logger); err != nil {
253+
d.logger.Error("failed to delete stale files",
254+
zap.Error(err),
255+
)
256+
}
257+
}
236258
}
237259
}
238260

pkg/container/file-helper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func createFile(filePath, base64FileContent string, logger *zap.Logger) (string,
4040
return filepath.Base(filePath), fmt.Errorf("failed to decode the file content: %w", err)
4141
}
4242

43-
n, err := f.Write([]byte(data))
43+
n, err := f.Write(data)
4444
if err != nil {
4545
return filepath.Base(filePath), fmt.Errorf("failed to write the content to the file: %w", err)
4646
}

pkg/container/types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ type Code struct {
2222

2323
type ContainerClient interface {
2424
FreeUpZombieContainers(ctx context.Context) error
25-
ExecuteCode(ctx context.Context, code *Code) (string, error) // Executes and returns the output in the string, error in case of server errors not code errors.
25+
26+
// Executes and returns the output in the string, error in case of server errors not code errors.
27+
ExecuteCode(ctx context.Context, code *Code) (string, error)
2628

2729
// TODO: Is this even needed?
28-
GetContainers(ctx context.Context, opts *container.ListOptions) ([]Container, error) // Remove list options if you want some other container type other than docker
30+
// Remove list options if you want some other container type other than docker
31+
GetContainers(ctx context.Context, opts *container.ListOptions) ([]Container, error)
2932
}

0 commit comments

Comments
 (0)