<commit_before>(ns rpi-challenger.app
  (:require [rpi-challenger.core.tournament :as t]
            [rpi-challenger.core.participant :as p]
            [rpi-challenger.core.strike :as strike]
            [rpi-challenger.core.challenges :as c]
            [rpi-challenger.http :as http]
            [rpi-challenger.util.io :as io]
            [rpi-challenger.util.threads :as threads])
  (:import [org.slf4j LoggerFactory Logger]
           [java.io File]
           [java.util.concurrent Executors Future ScheduledExecutorService TimeUnit]))

(def ^:dynamic logger (LoggerFactory/getLogger (str (ns-name *ns*))))

(def round-duration-in-seconds 60)

(def challenge-functions-dir (File. "../rpi-challenges/src/")) ; TODO: parameterize the dir on command line or create an admin screen

(def app-state-file (File. "rpi-challenger-state.clj"))


(defn make-app []
  (ref {:tournament (t/make-tournament)
        :scheduler (Executors/newScheduledThreadPool 1 (threads/daemon-thread-factory "round-scheduler"))
        :thread-pool (Executors/newCachedThreadPool (threads/daemon-thread-factory "participant-poller"))
        :participant-pollers []}))

(defn- alter-tournament [app f & args]
  (apply alter (concat [app update-in [:tournament ] f] args)))


; persistence

(defn save-state [app file]
  (io/object-to-file file (t/serialize (:tournament @app))))

(defn load-state [file]
  (let [app (make-app)
        tournament (t/deserialize (io/file-to-object file))]
    (dosync (alter-tournament app (fn [_] tournament)))
    app))


; participants

(defn ^:dynamic record-strike [app participant strike]
  (dosync
    (alter-tournament app t/record-strike participant strike)))

(defn poll-participant [app participant challenges]
  (if (not (empty? challenges))
    (let [challenge (first challenges)
          response (http/post-request (:url participant) (c/format-question challenge))
          strike (strike/make-strike response challenge)]
      (record-strike app participant strike)

      ; avoid 100% CPU usage if the URL is invalid (e.g. no such host)
      (if (strike/error? strike)
        (Thread/sleep 1000))

      ; stop on first failed challenge (to keep the harder challenges secret)
      (if (strike/hit? strike)
        (poll-participant app participant (rest challenges))))))

(defn ^:dynamic poll-participant-loop [app participant]
  (while (not (Thread/interrupted))
    (poll-participant app participant (t/generate-challenges (:tournament @app)))))

(defn ^:dynamic start-polling [app participant]
  (threads/execute (:thread-pool @app) #(poll-participant-loop app participant)))

(defn get-participants [app]
  (t/participants (:tournament @app)))

(defn get-participant-by-id [app id]
  (t/participant-by-id (:tournament @app) id))

(defn ^:dynamic register-participant [app name url]
  (.info logger "Registering participant \"{}\" at {}" name url)
  (start-polling app (dosync
                       (let [participant (t/make-participant (:tournament @app) name url)]
                         (alter-tournament app t/register-participant participant)
                         participant))))<commit_msg>Reduce CPU usage by sleeping a bit between each poll<commit_after>(ns rpi-challenger.app
  (:require [rpi-challenger.core.tournament :as t]
            [rpi-challenger.core.participant :as p]
            [rpi-challenger.core.strike :as strike]
            [rpi-challenger.core.challenges :as c]
            [rpi-challenger.http :as http]
            [rpi-challenger.util.io :as io]
            [rpi-challenger.util.threads :as threads])
  (:import [org.slf4j LoggerFactory Logger]
           [java.io File]
           [java.util.concurrent Executors Future ScheduledExecutorService TimeUnit]))

(def ^:dynamic logger (LoggerFactory/getLogger (str (ns-name *ns*))))

(def round-duration-in-seconds 60)

(def challenge-functions-dir (File. "../rpi-challenges/src/")) ; TODO: parameterize the dir on command line or create an admin screen

(def app-state-file (File. "rpi-challenger-state.clj"))


(defn make-app []
  (ref {:tournament (t/make-tournament)
        :scheduler (Executors/newScheduledThreadPool 1 (threads/daemon-thread-factory "round-scheduler"))
        :thread-pool (Executors/newCachedThreadPool (threads/daemon-thread-factory "participant-poller"))
        :participant-pollers []}))

(defn- alter-tournament [app f & args]
  (apply alter (concat [app update-in [:tournament ] f] args)))


; persistence

(defn save-state [app file]
  (io/object-to-file file (t/serialize (:tournament @app))))

(defn load-state [file]
  (let [app (make-app)
        tournament (t/deserialize (io/file-to-object file))]
    (dosync (alter-tournament app (fn [_] tournament)))
    app))


; participants

(defn ^:dynamic record-strike [app participant strike]
  (dosync
    (alter-tournament app t/record-strike participant strike)))

(defn poll-participant [app participant challenges]
  (if (not (empty? challenges))
    (let [challenge (first challenges)
          response (http/post-request (:url participant) (c/format-question challenge))
          strike (strike/make-strike response challenge)]
      (record-strike app participant strike)

      ; avoid 100% CPU usage if the URL is invalid (e.g. no such host)
      (if (strike/error? strike)
        (Thread/sleep 1000))

      ; reduce CPU usage also under normal circumstances
      (Thread/sleep 10)

      ; stop on first failed challenge (to keep the harder challenges secret)
      (if (strike/hit? strike)
        (poll-participant app participant (rest challenges))))))

(defn ^:dynamic poll-participant-loop [app participant]
  (while (not (Thread/interrupted))
    (poll-participant app participant (t/generate-challenges (:tournament @app)))))

(defn ^:dynamic start-polling [app participant]
  (threads/execute (:thread-pool @app) #(poll-participant-loop app participant)))

(defn get-participants [app]
  (t/participants (:tournament @app)))

(defn get-participant-by-id [app id]
  (t/participant-by-id (:tournament @app) id))

(defn ^:dynamic register-participant [app name url]
  (.info logger "Registering participant \"{}\" at {}" name url)
  (start-polling app (dosync
                       (let [participant (t/make-participant (:tournament @app) name url)]
                         (alter-tournament app t/register-participant participant)
                         participant))))
{'fraction_non_alphanumeric': 0.1302921418528355, 'fraction_numerical': 0.0037494141540384317, 'mean_word_length': 3.27369826435247, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2198256', 'n_tokens_mistral': 2098, 'n_tokens_neox': 1956, 'n_words': 543}