/*
* Copyright 2007 Xu, Chuan <xuchuan@gmail.com>
*
* This file is part of ZOJ.
*
* ZOJ 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 revision 3 of the License, or (at your option) any later revision.
*
* ZOJ 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 ZOJ. if not, see
* <http://www.gnu.org/licenses/>.
*/
package cn.edu.zju.acm.onlinejudge.judgeservice;
import java.util.concurrent.atomic.AtomicReference;
import cn.edu.zju.acm.onlinejudge.bean.Submission;
/**
* This class is a queue of all submissions being judged.
*
* All submissions are put in a single linked list. Removal operations only move the head reference forward without
* changing the internal linked list structure. Once a JudgingQueueIterator instance is created, it copies the current
* head reference. In this way, a JudgingQueueIterator instance can always traverse all nodes created subsequent to its
* construction.
*
* We expect this queue to be short. A submission is pushed into this queue when its judge starts and removed when judge
* ends. This is usually less than 1 minute. And we do not expect many judge threads using this queue.
*
* Nodes can be garbage collected when it is not reachable from the head and by any JudgingQueueIterator. So as long as
* we expect that the life of a JudgingQueueIterator stays short, like in seconds, this queue does not cost too much
* memory.
*
* This implementation is thread safe and lock-free just like ConcurrentLinkedQueue.
*
* The same submission may be push into this queue multiple times. When this happens, the linked list will contains
* multiple nodes with the same submission reference. It is safe excepting using a bit more memory. Usually this is not
* a problem.
*
*
* @author Xu, Chuan
* @version 1.0
*/
class JudgingQueue {
/**
* The head of this queue.
*/
private JudgingQueue.JudgingQueueNode head = new JudgingQueue.JudgingQueueNode();
/**
* The tail of this queue. Always points to an empty node.
*/
private AtomicReference<JudgingQueue.JudgingQueueNode> tail =
new AtomicReference<JudgingQueue.JudgingQueueNode>(this.head);
/**
* Adds a new submission.
*
* @param submission
* the submission to add
*/
public void push(Submission submission) {
if (submission == null) {
throw new NullPointerException("submission should not be null");
}
JudgingQueue.JudgingQueueNode node = new JudgingQueueNode();
JudgingQueue.JudgingQueueNode last = this.tail.getAndSet(node);
last.next = node;
last.submission = submission;
}
/**
* Remove the submission from this queue. It is done by marking the corresponding node and removed and move forward
* the head if necessary.
*
* NOTE: It is done by a scan of this queue. This queue is expected to be short so that this operation can be
* finished very quickly. We do have ways to implement this in constant time like using a hashmap or just add a
* field in Submission class and update the to mark it as removed. The former solution is a waste when the queue
* keeps short which is expected. The latter one hurts readability a lot. Some alternative designs similar to the
* latter solution exists, but they all have defects. Let me know if anyone has a good idea about the design.
*/
public void remove(Submission submission) {
// Find the submission and mark it as removed.
JudgingQueue.JudgingQueueNode node = this.head;
while (node.submission != null) {
if (node.submission == submission) {
node.removed = true;
break;
}
node = node.next;
}
// Find the last not-removed node.
node = this.head;
while (node.submission != null && node.removed) {
node = node.next;
}
// Not a problem when multiple threads trying to modify this value. The head will finally be moved forward.
this.head = node;
}
public JudgingQueueIterator iterator() {
return new JudgingQueueIteratorImpl(this.head);
}
private static class JudgingQueueNode {
Submission submission = null;
JudgingQueue.JudgingQueueNode next = null;
boolean removed = false;
}
private static class JudgingQueueIteratorImpl implements JudgingQueueIterator {
JudgingQueue.JudgingQueueNode head;
public JudgingQueueIteratorImpl(JudgingQueue.JudgingQueueNode head) {
this.head = head;
}
/**
* See JudgingQueueIterator.next
*/
public Submission next() {
Submission ret = this.head.submission;
if (ret != null) {
this.head = this.head.next;
}
return ret;
}
}
}