<commit_before>import nextTick from './nextTick'
import { runReaction } from './reactionRunner'

export const TARGET_FPS = 30
const PRIORITY = Symbol('reaction priority')
let reactionProcessingQueued = false
let lastRun = Date.now()

export const priorities = {
  CRITICAL: 'critical',
  HIGH: 'high',
  LOW: 'low'
}

const DEFAULT_PRIORITY = priorities.CRITICAL

const validPriorities = new Set(['critical', 'high', 'low'])

const queue = {
  [priorities.CRITICAL]: new Set(),
  [priorities.HIGH]: new Set(),
  [priorities.LOW]: new Set()
}

export function initReaction (reaction, priority) {
  reaction[PRIORITY] = priority = priority || DEFAULT_PRIORITY
  validatePriority(priority)

  if (priority === priorities.CRITICAL) {
    // critical reactions execute once synchronously on init
    runReaction(reaction)
  } else {
    // otherwise queue it to run later
    queueReaction(reaction)
  }
}

export function queueReaction (reaction) {
  const priority = reaction[PRIORITY]
  queue[priority].add(reaction)
  if (!reactionProcessingQueued) {
    nextTick(runQueuedReactions)
    reactionProcessingQueued = true
  }
}

export function getPriority (reaction) {
  return reaction[PRIORITY]
}

export function setPriority (reaction, priority) {
  validatePriority(priority)
  const prevPriority = reaction[PRIORITY]
  const prevQueue = queue[prevPriority]
  if (prevQueue.has(reaction)) {
    const nextQueue = queue[priority]
    nextQueue.add(reaction)
    prevQueue.delete(reaction)
  }
  reaction[PRIORITY] = priority
}

function validatePriority (priority) {
  if (!validPriorities.has(priority)) {
    throw new Error(`Invalid priority: ${priority}`)
  }
}

export function isReactionQueued (reaction) {
  const priority = reaction[PRIORITY]
  return queue[priority].has(reaction)
}

export function unqueueReaction (reaction) {
  const priority = reaction[PRIORITY]
  queue[priority].delete(reaction)
}

export function runQueuedReactions () {
  const interval = 1000 / TARGET_FPS

  // critical reactions must all execute before the next frame
  const criticalReactions = queue[priorities.CRITICAL]
  criticalReactions.forEach(runReaction)
  criticalReactions.clear()

  // high-prio reactions can run if there is free time remaining
  const isHighPrioEmpty = processQueue(priorities.HIGH, interval)
  // low-prio reactions can run if there is free time and no more high-prio reactions
  const isLowPrioEmpty = processQueue(priorities.LOW, interval)

  if (isHighPrioEmpty && isLowPrioEmpty) {
    reactionProcessingQueued = false
  } else {
    nextTick(runQueuedReactions)
  }
  lastRun = Date.now()
}

function processQueue (priority, interval) {
  const queueWithPriority = queue[priority]
  const iterator = queueWithPriority[Symbol.iterator]()
  let reaction = iterator.next()
  while ((Date.now() - lastRun) < interval) {
    if (reaction.done) {
      return true
    }
    runReaction(reaction.value)
    queueWithPriority.delete(reaction)
    reaction = iterator.next()
  }
}<commit_msg>fix (priority queue): remove stuck tasks<commit_after>import nextTick from './nextTick'
import { runReaction } from './reactionRunner'

export const TARGET_FPS = 30
const PRIORITY = Symbol('reaction priority')
let reactionProcessingQueued = false
let lastRun = Date.now()

export const priorities = {
  CRITICAL: 'critical',
  HIGH: 'high',
  LOW: 'low'
}

const DEFAULT_PRIORITY = priorities.CRITICAL

const validPriorities = new Set(['critical', 'high', 'low'])

const queue = {
  [priorities.CRITICAL]: new Set(),
  [priorities.HIGH]: new Set(),
  [priorities.LOW]: new Set()
}

export function initReaction (reaction, priority) {
  reaction[PRIORITY] = priority = priority || DEFAULT_PRIORITY
  validatePriority(priority)

  if (priority === priorities.CRITICAL) {
    // critical reactions execute once synchronously on init
    runReaction(reaction)
  } else {
    // otherwise queue it to run later
    queueReaction(reaction)
  }
}

export function queueReaction (reaction) {
  const priority = reaction[PRIORITY]
  queue[priority].add(reaction)
  if (!reactionProcessingQueued) {
    nextTick(runQueuedReactions)
    reactionProcessingQueued = true
  }
}

export function getPriority (reaction) {
  return reaction[PRIORITY]
}

export function setPriority (reaction, priority) {
  validatePriority(priority)
  const prevPriority = reaction[PRIORITY]
  const prevQueue = queue[prevPriority]
  if (prevQueue.has(reaction)) {
    const nextQueue = queue[priority]
    nextQueue.add(reaction)
    prevQueue.delete(reaction)
  }
  reaction[PRIORITY] = priority
}

function validatePriority (priority) {
  if (!validPriorities.has(priority)) {
    throw new Error(`Invalid priority: ${priority}`)
  }
}

export function isReactionQueued (reaction) {
  const priority = reaction[PRIORITY]
  return queue[priority].has(reaction)
}

export function unqueueReaction (reaction) {
  const priority = reaction[PRIORITY]
  queue[priority].delete(reaction)
}

export function runQueuedReactions () {
  const interval = 1000 / TARGET_FPS

  // critical reactions must all execute before the next frame
  const criticalReactions = queue[priorities.CRITICAL]
  criticalReactions.forEach(runReaction)
  criticalReactions.clear()

  // high-prio reactions can run if there is free time remaining
  const isHighPrioEmpty = processQueue(priorities.HIGH, interval)
  // low-prio reactions can run if there is free time and no more high-prio reactions
  const isLowPrioEmpty = processQueue(priorities.LOW, interval)

  if (isHighPrioEmpty && isLowPrioEmpty) {
    reactionProcessingQueued = false
  } else {
    nextTick(runQueuedReactions)
  }
  lastRun = Date.now()
}

function processQueue (priority, interval) {
  const queueWithPriority = queue[priority]
  const iterator = queueWithPriority[Symbol.iterator]()
  let reaction = iterator.next()
  while ((Date.now() - lastRun) < interval) {
    if (reaction.done) {
      return true
    }
    runReaction(reaction.value)
    queueWithPriority.delete(reaction.value)
    reaction = iterator.next()
  }
}
{'fraction_non_alphanumeric': 0.10080978350685837, 'fraction_numerical': 0.0019831432821021317, 'mean_word_length': 4.526940639269406, 'pattern_counts': {'":': 0, '<': 5, '<?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': '4079614', 'n_tokens_mistral': 1856, 'n_tokens_neox': 1774, 'n_words': 473}