Sobes.tech
Middle

// Description: // Implement the backend part of a scheduler application, which will have 3 methods: // 1) Create a command with an ID and a start time. // 2) Get a list of active commands. // 3) Cancel a command by ID. // The command should execute exactly at the time passed in the first method. // The methods will be called via an HTTP server, the number of concurrent requests // to these methods is not defined. // The execution of the command can be simulated by Print(ID) to stdout, to emulate execution.

sobes.tech AI

Answer from AI

To implement the backend part of the scheduler with three methods in Go, you can use an HTTP server with handlers for creating, retrieving, and canceling commands. For precise execution of commands at a specified time, it is convenient to use timers or a task scheduler.

Main points:

  • Store commands in a thread-safe structure (e.g., sync.Map or with a mutex).
  • When creating a command, start a timer that will trigger at the required time and output the ID.
  • The method for retrieving active commands returns a list of commands that are not yet executed or canceled.
  • The cancel method removes the command from storage and cancels the timer.

An example of a simplified implementation:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"
)

type Command struct {
	ID       string    `json:"id"`
	ExecTime time.Time `json:"exec_time"`
	timer    *time.Timer
}

var (
	commands = make(map[string]*Command)
	mu       sync.Mutex
)

func createCommand(w http.ResponseWriter, r *http.Request) {
	var cmd Command
	if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil {
		http.Error(w, "Invalid input", http.StatusBadRequest)
		return
	}

	mu.Lock()
	defer mu.Unlock()

	if _, exists := commands[cmd.ID]; exists {
		http.Error(w, "Command already exists", http.StatusBadRequest)
		return
	}

	dur := time.Until(cmd.ExecTime)
	if dur <= 0 {
		http.Error(w, "ExecTime must be in the future", http.StatusBadRequest)
		return
	}

	cmd.timer = time.AfterFunc(dur, func() {
		fmt.Println(cmd.ID)
		mu.Lock()
		delete(commands, cmd.ID)
		mu.Unlock()
	})

	commands[cmd.ID] = &cmd
	w.WriteHeader(http.StatusCreated)
}

func listCommands(w http.ResponseWriter, r *http.Request) {
	mu.Lock()
	defer mu.Unlock()

	active := make([]Command, 0, len(commands))
	for _, cmd := range commands {
		active = append(active, Command{ID: cmd.ID, ExecTime: cmd.ExecTime})
	}

	json.NewEncoder(w).Encode(active)
}

func cancelCommand(w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	if id == "" {
		http.Error(w, "Missing id parameter", http.StatusBadRequest)
		return
	}

	mu.Lock()
	defer mu.Unlock()

	cmd, exists := commands[id]
	if !exists {
		http.Error(w, "Command not found", http.StatusNotFound)
		return
	}

	cmd.timer.Stop()
	delete(commands, id)
	w.WriteHeader(http.StatusOK)
}

func main() {
	http.HandleFunc("/create", createCommand)
	http.HandleFunc("/list", listCommands)
	http.HandleFunc("/cancel", cancelCommand)

	log.Println("Server started on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

This approach ensures precise execution of commands at the specified time, thread safety, and the ability to cancel.