O(1) lock free container The 2019 Stack Overflow Developer Survey Results Are InLock-Free Ring...
Carnot-Caratheodory metric
How to make payment on the internet without leaving a money trail?
How to change the limits of integration
The difference between dialogue marks
What does Linus Torvalds means when he says that git "never ever" tracks a file?
"What time...?" or "At what time...?" - what is more grammatically correct?
Geography at the pixel level
What is the best strategy for white in this position?
Why don't Unix/Linux systems traverse through directories until they find the required version of a linked library?
Why Did Howard Stark Use All The Vibranium They Had On A Prototype Shield?
How to create dashed lines/arrows in Illustrator
A poker game description that does not feel gimmicky
Should I write numbers in words or as numerals when there are multiple next to each other?
How to reverse every other sublist of a list?
Inflated grade on resume at previous job, might former employer tell new employer?
What do hard-Brexiteers want with respect to the Irish border?
How was Skylab's orbit inclination chosen?
What does "rabbited" mean/imply in this sentence?
How to manage monthly salary
How can I fix this gap between bookcases I made?
Monty Hall variation
Limit the amount of RAM Mathematica may access?
Pristine Bit Checking
It's possible to achieve negative score?
O(1) lock free container
The 2019 Stack Overflow Developer Survey Results Are InLock-Free Ring ImplementationLock-Free Ring ImplementationLock-free DoubleBufferedListLock-free multiple-consumer multiple-producer queueMax heap in Javalock-free job queue without size restriction (multiple read/write)Lock-free ringbuffer with multiple readers in C++11Implementation of a lock-free fixed-sized allocatorFast insert unique containerCustomized lock-free RingBufferLeetcode #146. LRUCache solution in Java (Doubly Linked List + HashMap)
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}
$begingroup$
This is a lock-free Container
class. By Container
I mean it will hold objects for iteration at a later time. To achieve $O(1)$ it returns a token from the put
method which you can then use to remove the object. The token is actually the node in which the object has been placed.
It is instantiated with a fixed size and throws an exception if capacity is exhausted. Internally it uses a ring buffer.
It is not intended as a candidate for the Collections
toolbox.
Please try to find a way to break it if you can, or point out any areas where you think it might fail under certain situations.
/**
* Container
* ---------
*
* A lock-free container that offers a close-to O(1) add/remove performance.
*
*/
public class Container<T> implements Iterable<T> {
// The capacity of the container.
final int capacity;
// The list.
AtomicReference<Node<T>> head = new AtomicReference<Node<T>>();
// Constructor
public Container(int capacity) {
this.capacity = capacity;
// Construct the list.
Node<T> h = new Node<T>();
Node<T> it = h;
// One created, now add (capacity - 1) more
for (int i = 0; i < capacity - 1; i++) {
// Add it.
it.next = new Node<T>();
// Step on to it.
it = it.next;
}
// Make it a ring.
it.next = h;
// Install it.
head.set(h);
}
// Empty ... NOT thread safe.
public void clear() {
Node<T> it = head.get();
for (int i = 0; i < capacity; i++) {
// Trash the element
it.element = null;
// Mark it free.
it.free.set(true);
it = it.next;
}
}
// Add a new one.
public Node<T> add(T element) {
// Get a free node and attach the element.
return getFree().attach(element);
}
// Find the next free element and mark it not free.
private Node<T> getFree() {
Node<T> freeNode = head.get();
int skipped = 0;
// Stop when we hit the end of the list
// ... or we successfully transit a node from free to not-free.
while (skipped < capacity && !freeNode.free.compareAndSet(true, false)) {
skipped += 1;
freeNode = freeNode.next;
}
if (skipped < capacity) {
// Put the head as next.
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
} else {
// We hit the end! No more free nodes.
throw new IllegalStateException("Capacity exhausted.");
}
return freeNode;
}
// Mark it free.
public void remove(Node<T> it, T element) {
// Remove the element first.
it.detach(element);
// Mark it as free.
if (!it.free.compareAndSet(false, true)) {
throw new IllegalStateException("Freeing a freed node.");
}
}
// The Node class. It is static so needs the <T> repeated.
public static class Node<T> {
// The element in the node.
private T element;
// Are we free?
private AtomicBoolean free = new AtomicBoolean(true);
// The next reference in whatever list I am in.
private Node<T> next;
// Construct a node of the list
private Node() {
// Start empty.
element = null;
}
// Attach the element.
public Node<T> attach(T element) {
// Sanity check.
if (this.element == null) {
this.element = element;
} else {
throw new IllegalArgumentException("There is already an element attached.");
}
// Useful for chaining.
return this;
}
// Detach the element.
public Node<T> detach(T element) {
// Sanity check.
if (this.element == element) {
this.element = null;
} else {
throw new IllegalArgumentException("Removal of wrong element.");
}
// Useful for chaining.
return this;
}
public T get () {
return element;
}
@Override
public String toString() {
return element != null ? element.toString() : "null";
}
}
// Provides an iterator across all items in the container.
@Override
public Iterator<T> iterator() {
return new UsedNodesIterator<T>(this);
}
// Iterates across used nodes.
private static class UsedNodesIterator<T> implements Iterator<T> {
// Where next to look for the next used node.
Node<T> it;
int limit = 0;
T next = null;
public UsedNodesIterator(Container<T> c) {
// Snapshot the head node at this time.
it = c.head.get();
limit = c.capacity;
}
@Override
public boolean hasNext() {
// Made into a `while` loop to fix issue reported by @Nim
while (next == null && limit > 0) {
// Scan to the next non-free node.
while (limit > 0 && it.free.get() == true) {
it = it.next;
// Step down 1.
limit -= 1;
}
if (limit != 0) {
next = it.element;
}
}
return next != null;
}
@Override
public T next() {
T n = null;
if ( hasNext () ) {
// Give it to them.
n = next;
next = null;
// Step forward.
it = it.next;
limit -= 1;
} else {
// Not there!!
throw new NoSuchElementException ();
}
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
}
Having posted this request I now find myself searching aggressively for areas of doubt in the code. Some of those follow but I plan to edit the code and remove these as and when you point me in the right direction.
- Should
Node<T>.element
bevolatile
? I think perhaps. - Should
Node<T>.next
bevolatile
? I think not but I am not sure why. - Have I chosen a good method for the iterator? I picked a counter rather than seeing the head for the second time to ensure I am resilient to an increase/decrease in size of the ring. This has the small downside of having slightly less predictability but it guarantees termination and is only a bit strange under high parallelism.
NB: Container
is not intended to be used between a producer and a consumer. It is designed more for keeping a note of which objects have a particular feature in an iterable way. I use it to keep track of threads in the process of calling put
on a BlockingQueue
(which may of course block) so I can interrupt them if the queue needs to close.
java collections circular-list lock-free atomic
$endgroup$
add a comment |
$begingroup$
This is a lock-free Container
class. By Container
I mean it will hold objects for iteration at a later time. To achieve $O(1)$ it returns a token from the put
method which you can then use to remove the object. The token is actually the node in which the object has been placed.
It is instantiated with a fixed size and throws an exception if capacity is exhausted. Internally it uses a ring buffer.
It is not intended as a candidate for the Collections
toolbox.
Please try to find a way to break it if you can, or point out any areas where you think it might fail under certain situations.
/**
* Container
* ---------
*
* A lock-free container that offers a close-to O(1) add/remove performance.
*
*/
public class Container<T> implements Iterable<T> {
// The capacity of the container.
final int capacity;
// The list.
AtomicReference<Node<T>> head = new AtomicReference<Node<T>>();
// Constructor
public Container(int capacity) {
this.capacity = capacity;
// Construct the list.
Node<T> h = new Node<T>();
Node<T> it = h;
// One created, now add (capacity - 1) more
for (int i = 0; i < capacity - 1; i++) {
// Add it.
it.next = new Node<T>();
// Step on to it.
it = it.next;
}
// Make it a ring.
it.next = h;
// Install it.
head.set(h);
}
// Empty ... NOT thread safe.
public void clear() {
Node<T> it = head.get();
for (int i = 0; i < capacity; i++) {
// Trash the element
it.element = null;
// Mark it free.
it.free.set(true);
it = it.next;
}
}
// Add a new one.
public Node<T> add(T element) {
// Get a free node and attach the element.
return getFree().attach(element);
}
// Find the next free element and mark it not free.
private Node<T> getFree() {
Node<T> freeNode = head.get();
int skipped = 0;
// Stop when we hit the end of the list
// ... or we successfully transit a node from free to not-free.
while (skipped < capacity && !freeNode.free.compareAndSet(true, false)) {
skipped += 1;
freeNode = freeNode.next;
}
if (skipped < capacity) {
// Put the head as next.
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
} else {
// We hit the end! No more free nodes.
throw new IllegalStateException("Capacity exhausted.");
}
return freeNode;
}
// Mark it free.
public void remove(Node<T> it, T element) {
// Remove the element first.
it.detach(element);
// Mark it as free.
if (!it.free.compareAndSet(false, true)) {
throw new IllegalStateException("Freeing a freed node.");
}
}
// The Node class. It is static so needs the <T> repeated.
public static class Node<T> {
// The element in the node.
private T element;
// Are we free?
private AtomicBoolean free = new AtomicBoolean(true);
// The next reference in whatever list I am in.
private Node<T> next;
// Construct a node of the list
private Node() {
// Start empty.
element = null;
}
// Attach the element.
public Node<T> attach(T element) {
// Sanity check.
if (this.element == null) {
this.element = element;
} else {
throw new IllegalArgumentException("There is already an element attached.");
}
// Useful for chaining.
return this;
}
// Detach the element.
public Node<T> detach(T element) {
// Sanity check.
if (this.element == element) {
this.element = null;
} else {
throw new IllegalArgumentException("Removal of wrong element.");
}
// Useful for chaining.
return this;
}
public T get () {
return element;
}
@Override
public String toString() {
return element != null ? element.toString() : "null";
}
}
// Provides an iterator across all items in the container.
@Override
public Iterator<T> iterator() {
return new UsedNodesIterator<T>(this);
}
// Iterates across used nodes.
private static class UsedNodesIterator<T> implements Iterator<T> {
// Where next to look for the next used node.
Node<T> it;
int limit = 0;
T next = null;
public UsedNodesIterator(Container<T> c) {
// Snapshot the head node at this time.
it = c.head.get();
limit = c.capacity;
}
@Override
public boolean hasNext() {
// Made into a `while` loop to fix issue reported by @Nim
while (next == null && limit > 0) {
// Scan to the next non-free node.
while (limit > 0 && it.free.get() == true) {
it = it.next;
// Step down 1.
limit -= 1;
}
if (limit != 0) {
next = it.element;
}
}
return next != null;
}
@Override
public T next() {
T n = null;
if ( hasNext () ) {
// Give it to them.
n = next;
next = null;
// Step forward.
it = it.next;
limit -= 1;
} else {
// Not there!!
throw new NoSuchElementException ();
}
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
}
Having posted this request I now find myself searching aggressively for areas of doubt in the code. Some of those follow but I plan to edit the code and remove these as and when you point me in the right direction.
- Should
Node<T>.element
bevolatile
? I think perhaps. - Should
Node<T>.next
bevolatile
? I think not but I am not sure why. - Have I chosen a good method for the iterator? I picked a counter rather than seeing the head for the second time to ensure I am resilient to an increase/decrease in size of the ring. This has the small downside of having slightly less predictability but it guarantees termination and is only a bit strange under high parallelism.
NB: Container
is not intended to be used between a producer and a consumer. It is designed more for keeping a note of which objects have a particular feature in an iterable way. I use it to keep track of threads in the process of calling put
on a BlockingQueue
(which may of course block) so I can interrupt them if the queue needs to close.
java collections circular-list lock-free atomic
$endgroup$
$begingroup$
The original post is here.
$endgroup$
– OldCurmudgeon
Jun 18 '12 at 13:38
$begingroup$
The declaration ofstop
variable seems to be missing.
$endgroup$
– rahul
Jun 21 '12 at 16:26
$begingroup$
@blufox - my apologies - it should have readlimit
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 18:13
add a comment |
$begingroup$
This is a lock-free Container
class. By Container
I mean it will hold objects for iteration at a later time. To achieve $O(1)$ it returns a token from the put
method which you can then use to remove the object. The token is actually the node in which the object has been placed.
It is instantiated with a fixed size and throws an exception if capacity is exhausted. Internally it uses a ring buffer.
It is not intended as a candidate for the Collections
toolbox.
Please try to find a way to break it if you can, or point out any areas where you think it might fail under certain situations.
/**
* Container
* ---------
*
* A lock-free container that offers a close-to O(1) add/remove performance.
*
*/
public class Container<T> implements Iterable<T> {
// The capacity of the container.
final int capacity;
// The list.
AtomicReference<Node<T>> head = new AtomicReference<Node<T>>();
// Constructor
public Container(int capacity) {
this.capacity = capacity;
// Construct the list.
Node<T> h = new Node<T>();
Node<T> it = h;
// One created, now add (capacity - 1) more
for (int i = 0; i < capacity - 1; i++) {
// Add it.
it.next = new Node<T>();
// Step on to it.
it = it.next;
}
// Make it a ring.
it.next = h;
// Install it.
head.set(h);
}
// Empty ... NOT thread safe.
public void clear() {
Node<T> it = head.get();
for (int i = 0; i < capacity; i++) {
// Trash the element
it.element = null;
// Mark it free.
it.free.set(true);
it = it.next;
}
}
// Add a new one.
public Node<T> add(T element) {
// Get a free node and attach the element.
return getFree().attach(element);
}
// Find the next free element and mark it not free.
private Node<T> getFree() {
Node<T> freeNode = head.get();
int skipped = 0;
// Stop when we hit the end of the list
// ... or we successfully transit a node from free to not-free.
while (skipped < capacity && !freeNode.free.compareAndSet(true, false)) {
skipped += 1;
freeNode = freeNode.next;
}
if (skipped < capacity) {
// Put the head as next.
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
} else {
// We hit the end! No more free nodes.
throw new IllegalStateException("Capacity exhausted.");
}
return freeNode;
}
// Mark it free.
public void remove(Node<T> it, T element) {
// Remove the element first.
it.detach(element);
// Mark it as free.
if (!it.free.compareAndSet(false, true)) {
throw new IllegalStateException("Freeing a freed node.");
}
}
// The Node class. It is static so needs the <T> repeated.
public static class Node<T> {
// The element in the node.
private T element;
// Are we free?
private AtomicBoolean free = new AtomicBoolean(true);
// The next reference in whatever list I am in.
private Node<T> next;
// Construct a node of the list
private Node() {
// Start empty.
element = null;
}
// Attach the element.
public Node<T> attach(T element) {
// Sanity check.
if (this.element == null) {
this.element = element;
} else {
throw new IllegalArgumentException("There is already an element attached.");
}
// Useful for chaining.
return this;
}
// Detach the element.
public Node<T> detach(T element) {
// Sanity check.
if (this.element == element) {
this.element = null;
} else {
throw new IllegalArgumentException("Removal of wrong element.");
}
// Useful for chaining.
return this;
}
public T get () {
return element;
}
@Override
public String toString() {
return element != null ? element.toString() : "null";
}
}
// Provides an iterator across all items in the container.
@Override
public Iterator<T> iterator() {
return new UsedNodesIterator<T>(this);
}
// Iterates across used nodes.
private static class UsedNodesIterator<T> implements Iterator<T> {
// Where next to look for the next used node.
Node<T> it;
int limit = 0;
T next = null;
public UsedNodesIterator(Container<T> c) {
// Snapshot the head node at this time.
it = c.head.get();
limit = c.capacity;
}
@Override
public boolean hasNext() {
// Made into a `while` loop to fix issue reported by @Nim
while (next == null && limit > 0) {
// Scan to the next non-free node.
while (limit > 0 && it.free.get() == true) {
it = it.next;
// Step down 1.
limit -= 1;
}
if (limit != 0) {
next = it.element;
}
}
return next != null;
}
@Override
public T next() {
T n = null;
if ( hasNext () ) {
// Give it to them.
n = next;
next = null;
// Step forward.
it = it.next;
limit -= 1;
} else {
// Not there!!
throw new NoSuchElementException ();
}
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
}
Having posted this request I now find myself searching aggressively for areas of doubt in the code. Some of those follow but I plan to edit the code and remove these as and when you point me in the right direction.
- Should
Node<T>.element
bevolatile
? I think perhaps. - Should
Node<T>.next
bevolatile
? I think not but I am not sure why. - Have I chosen a good method for the iterator? I picked a counter rather than seeing the head for the second time to ensure I am resilient to an increase/decrease in size of the ring. This has the small downside of having slightly less predictability but it guarantees termination and is only a bit strange under high parallelism.
NB: Container
is not intended to be used between a producer and a consumer. It is designed more for keeping a note of which objects have a particular feature in an iterable way. I use it to keep track of threads in the process of calling put
on a BlockingQueue
(which may of course block) so I can interrupt them if the queue needs to close.
java collections circular-list lock-free atomic
$endgroup$
This is a lock-free Container
class. By Container
I mean it will hold objects for iteration at a later time. To achieve $O(1)$ it returns a token from the put
method which you can then use to remove the object. The token is actually the node in which the object has been placed.
It is instantiated with a fixed size and throws an exception if capacity is exhausted. Internally it uses a ring buffer.
It is not intended as a candidate for the Collections
toolbox.
Please try to find a way to break it if you can, or point out any areas where you think it might fail under certain situations.
/**
* Container
* ---------
*
* A lock-free container that offers a close-to O(1) add/remove performance.
*
*/
public class Container<T> implements Iterable<T> {
// The capacity of the container.
final int capacity;
// The list.
AtomicReference<Node<T>> head = new AtomicReference<Node<T>>();
// Constructor
public Container(int capacity) {
this.capacity = capacity;
// Construct the list.
Node<T> h = new Node<T>();
Node<T> it = h;
// One created, now add (capacity - 1) more
for (int i = 0; i < capacity - 1; i++) {
// Add it.
it.next = new Node<T>();
// Step on to it.
it = it.next;
}
// Make it a ring.
it.next = h;
// Install it.
head.set(h);
}
// Empty ... NOT thread safe.
public void clear() {
Node<T> it = head.get();
for (int i = 0; i < capacity; i++) {
// Trash the element
it.element = null;
// Mark it free.
it.free.set(true);
it = it.next;
}
}
// Add a new one.
public Node<T> add(T element) {
// Get a free node and attach the element.
return getFree().attach(element);
}
// Find the next free element and mark it not free.
private Node<T> getFree() {
Node<T> freeNode = head.get();
int skipped = 0;
// Stop when we hit the end of the list
// ... or we successfully transit a node from free to not-free.
while (skipped < capacity && !freeNode.free.compareAndSet(true, false)) {
skipped += 1;
freeNode = freeNode.next;
}
if (skipped < capacity) {
// Put the head as next.
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
} else {
// We hit the end! No more free nodes.
throw new IllegalStateException("Capacity exhausted.");
}
return freeNode;
}
// Mark it free.
public void remove(Node<T> it, T element) {
// Remove the element first.
it.detach(element);
// Mark it as free.
if (!it.free.compareAndSet(false, true)) {
throw new IllegalStateException("Freeing a freed node.");
}
}
// The Node class. It is static so needs the <T> repeated.
public static class Node<T> {
// The element in the node.
private T element;
// Are we free?
private AtomicBoolean free = new AtomicBoolean(true);
// The next reference in whatever list I am in.
private Node<T> next;
// Construct a node of the list
private Node() {
// Start empty.
element = null;
}
// Attach the element.
public Node<T> attach(T element) {
// Sanity check.
if (this.element == null) {
this.element = element;
} else {
throw new IllegalArgumentException("There is already an element attached.");
}
// Useful for chaining.
return this;
}
// Detach the element.
public Node<T> detach(T element) {
// Sanity check.
if (this.element == element) {
this.element = null;
} else {
throw new IllegalArgumentException("Removal of wrong element.");
}
// Useful for chaining.
return this;
}
public T get () {
return element;
}
@Override
public String toString() {
return element != null ? element.toString() : "null";
}
}
// Provides an iterator across all items in the container.
@Override
public Iterator<T> iterator() {
return new UsedNodesIterator<T>(this);
}
// Iterates across used nodes.
private static class UsedNodesIterator<T> implements Iterator<T> {
// Where next to look for the next used node.
Node<T> it;
int limit = 0;
T next = null;
public UsedNodesIterator(Container<T> c) {
// Snapshot the head node at this time.
it = c.head.get();
limit = c.capacity;
}
@Override
public boolean hasNext() {
// Made into a `while` loop to fix issue reported by @Nim
while (next == null && limit > 0) {
// Scan to the next non-free node.
while (limit > 0 && it.free.get() == true) {
it = it.next;
// Step down 1.
limit -= 1;
}
if (limit != 0) {
next = it.element;
}
}
return next != null;
}
@Override
public T next() {
T n = null;
if ( hasNext () ) {
// Give it to them.
n = next;
next = null;
// Step forward.
it = it.next;
limit -= 1;
} else {
// Not there!!
throw new NoSuchElementException ();
}
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
}
Having posted this request I now find myself searching aggressively for areas of doubt in the code. Some of those follow but I plan to edit the code and remove these as and when you point me in the right direction.
- Should
Node<T>.element
bevolatile
? I think perhaps. - Should
Node<T>.next
bevolatile
? I think not but I am not sure why. - Have I chosen a good method for the iterator? I picked a counter rather than seeing the head for the second time to ensure I am resilient to an increase/decrease in size of the ring. This has the small downside of having slightly less predictability but it guarantees termination and is only a bit strange under high parallelism.
NB: Container
is not intended to be used between a producer and a consumer. It is designed more for keeping a note of which objects have a particular feature in an iterable way. I use it to keep track of threads in the process of calling put
on a BlockingQueue
(which may of course block) so I can interrupt them if the queue needs to close.
java collections circular-list lock-free atomic
java collections circular-list lock-free atomic
edited Apr 6 '15 at 1:33
Jamal♦
30.6k11121227
30.6k11121227
asked Jun 18 '12 at 13:36
OldCurmudgeonOldCurmudgeon
1,49821527
1,49821527
$begingroup$
The original post is here.
$endgroup$
– OldCurmudgeon
Jun 18 '12 at 13:38
$begingroup$
The declaration ofstop
variable seems to be missing.
$endgroup$
– rahul
Jun 21 '12 at 16:26
$begingroup$
@blufox - my apologies - it should have readlimit
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 18:13
add a comment |
$begingroup$
The original post is here.
$endgroup$
– OldCurmudgeon
Jun 18 '12 at 13:38
$begingroup$
The declaration ofstop
variable seems to be missing.
$endgroup$
– rahul
Jun 21 '12 at 16:26
$begingroup$
@blufox - my apologies - it should have readlimit
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 18:13
$begingroup$
The original post is here.
$endgroup$
– OldCurmudgeon
Jun 18 '12 at 13:38
$begingroup$
The original post is here.
$endgroup$
– OldCurmudgeon
Jun 18 '12 at 13:38
$begingroup$
The declaration of
stop
variable seems to be missing.$endgroup$
– rahul
Jun 21 '12 at 16:26
$begingroup$
The declaration of
stop
variable seems to be missing.$endgroup$
– rahul
Jun 21 '12 at 16:26
$begingroup$
@blufox - my apologies - it should have read
limit
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 18:13
$begingroup$
@blufox - my apologies - it should have read
limit
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 18:13
add a comment |
2 Answers
2
active
oldest
votes
$begingroup$
I thought about a comment, but too long, several things I can think of:
This does not guarantee order, i.e. an item is entered into the next free slot, and if there is a fast producer, the order the consumer sees is not guaranteed. Is this intentional?
There is no atomic combined detach/attach operation, this means that for example:
Thread A: Attach starts, callsgetFree()
, which marks the node as not free (and is switched out before the element reference is set)
Thread B: is iterating, and hits this node, sees it's not free, so valid node, and returns this, if access to element is done (without a null pointer check), will result in an exception - I guess this is okay(?) Or should you test in your iterator and throw a ConcurrentModification?
I think it's simpler if you maintain an AtomicReference
in the node to the element and test that rather than a separate boolean and the reference.
Edit 1:
Looking at this:
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
Is this necessarily correct? If Thread A sets $n+1$ as head at the same time as Thread B setting n as head, and B succeeds - is the ring in a consistent state? I guess the no ordering guarantee means that this doesn't matter too much, but it would violate the $O(1)$ claim! ;) In fact, I would question the whole $O(1)$ claim, this is at best $O(1)$ and at worst $O(n)$...
$endgroup$
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in theContainer
throughout iteration time will be offered to theIterator
user. Anything that is added/removed to/from theContainer
during iteration may or may not be presented to the user.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
1
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false fromhasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that theNode
class ispublic
and exposes itsattach
anddetach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
I hold theAtomicBoolean free
in theNode
to allow for the above activity (instantdetach/attach
). I will investigate your idea to see how it affects the algorithm.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
2. (revisited) On further thought, your scenario may result innull
being returned by thenext
method but will not result in a breach of contract as the iteration will continue becausenext
is aNode
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time betweenhasNext
andnext
.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
3
$begingroup$
btw - I think the standard practice with Iterator is that you would callhasNext()
and if this is true, callnext()
to get the element, you should not invokehasNext()
within the call tonext()
,next()
should just return the element.
$endgroup$
– Nim
Jun 21 '12 at 14:37
|
show 3 more comments
$begingroup$
If thread A does Container.remove()
and then gets interrupted by thread B after the .detach()
but before the //Mark it free
, and thread B then iterates, it's going to get a ref to an item that's null
, which is probably Not What You Want.
$endgroup$
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return anull
value. In your case the null value will be skipped.
$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f12691%2fo1-lock-free-container%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
I thought about a comment, but too long, several things I can think of:
This does not guarantee order, i.e. an item is entered into the next free slot, and if there is a fast producer, the order the consumer sees is not guaranteed. Is this intentional?
There is no atomic combined detach/attach operation, this means that for example:
Thread A: Attach starts, callsgetFree()
, which marks the node as not free (and is switched out before the element reference is set)
Thread B: is iterating, and hits this node, sees it's not free, so valid node, and returns this, if access to element is done (without a null pointer check), will result in an exception - I guess this is okay(?) Or should you test in your iterator and throw a ConcurrentModification?
I think it's simpler if you maintain an AtomicReference
in the node to the element and test that rather than a separate boolean and the reference.
Edit 1:
Looking at this:
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
Is this necessarily correct? If Thread A sets $n+1$ as head at the same time as Thread B setting n as head, and B succeeds - is the ring in a consistent state? I guess the no ordering guarantee means that this doesn't matter too much, but it would violate the $O(1)$ claim! ;) In fact, I would question the whole $O(1)$ claim, this is at best $O(1)$ and at worst $O(n)$...
$endgroup$
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in theContainer
throughout iteration time will be offered to theIterator
user. Anything that is added/removed to/from theContainer
during iteration may or may not be presented to the user.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
1
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false fromhasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that theNode
class ispublic
and exposes itsattach
anddetach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
I hold theAtomicBoolean free
in theNode
to allow for the above activity (instantdetach/attach
). I will investigate your idea to see how it affects the algorithm.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
2. (revisited) On further thought, your scenario may result innull
being returned by thenext
method but will not result in a breach of contract as the iteration will continue becausenext
is aNode
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time betweenhasNext
andnext
.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
3
$begingroup$
btw - I think the standard practice with Iterator is that you would callhasNext()
and if this is true, callnext()
to get the element, you should not invokehasNext()
within the call tonext()
,next()
should just return the element.
$endgroup$
– Nim
Jun 21 '12 at 14:37
|
show 3 more comments
$begingroup$
I thought about a comment, but too long, several things I can think of:
This does not guarantee order, i.e. an item is entered into the next free slot, and if there is a fast producer, the order the consumer sees is not guaranteed. Is this intentional?
There is no atomic combined detach/attach operation, this means that for example:
Thread A: Attach starts, callsgetFree()
, which marks the node as not free (and is switched out before the element reference is set)
Thread B: is iterating, and hits this node, sees it's not free, so valid node, and returns this, if access to element is done (without a null pointer check), will result in an exception - I guess this is okay(?) Or should you test in your iterator and throw a ConcurrentModification?
I think it's simpler if you maintain an AtomicReference
in the node to the element and test that rather than a separate boolean and the reference.
Edit 1:
Looking at this:
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
Is this necessarily correct? If Thread A sets $n+1$ as head at the same time as Thread B setting n as head, and B succeeds - is the ring in a consistent state? I guess the no ordering guarantee means that this doesn't matter too much, but it would violate the $O(1)$ claim! ;) In fact, I would question the whole $O(1)$ claim, this is at best $O(1)$ and at worst $O(n)$...
$endgroup$
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in theContainer
throughout iteration time will be offered to theIterator
user. Anything that is added/removed to/from theContainer
during iteration may or may not be presented to the user.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
1
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false fromhasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that theNode
class ispublic
and exposes itsattach
anddetach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
I hold theAtomicBoolean free
in theNode
to allow for the above activity (instantdetach/attach
). I will investigate your idea to see how it affects the algorithm.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
2. (revisited) On further thought, your scenario may result innull
being returned by thenext
method but will not result in a breach of contract as the iteration will continue becausenext
is aNode
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time betweenhasNext
andnext
.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
3
$begingroup$
btw - I think the standard practice with Iterator is that you would callhasNext()
and if this is true, callnext()
to get the element, you should not invokehasNext()
within the call tonext()
,next()
should just return the element.
$endgroup$
– Nim
Jun 21 '12 at 14:37
|
show 3 more comments
$begingroup$
I thought about a comment, but too long, several things I can think of:
This does not guarantee order, i.e. an item is entered into the next free slot, and if there is a fast producer, the order the consumer sees is not guaranteed. Is this intentional?
There is no atomic combined detach/attach operation, this means that for example:
Thread A: Attach starts, callsgetFree()
, which marks the node as not free (and is switched out before the element reference is set)
Thread B: is iterating, and hits this node, sees it's not free, so valid node, and returns this, if access to element is done (without a null pointer check), will result in an exception - I guess this is okay(?) Or should you test in your iterator and throw a ConcurrentModification?
I think it's simpler if you maintain an AtomicReference
in the node to the element and test that rather than a separate boolean and the reference.
Edit 1:
Looking at this:
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
Is this necessarily correct? If Thread A sets $n+1$ as head at the same time as Thread B setting n as head, and B succeeds - is the ring in a consistent state? I guess the no ordering guarantee means that this doesn't matter too much, but it would violate the $O(1)$ claim! ;) In fact, I would question the whole $O(1)$ claim, this is at best $O(1)$ and at worst $O(n)$...
$endgroup$
I thought about a comment, but too long, several things I can think of:
This does not guarantee order, i.e. an item is entered into the next free slot, and if there is a fast producer, the order the consumer sees is not guaranteed. Is this intentional?
There is no atomic combined detach/attach operation, this means that for example:
Thread A: Attach starts, callsgetFree()
, which marks the node as not free (and is switched out before the element reference is set)
Thread B: is iterating, and hits this node, sees it's not free, so valid node, and returns this, if access to element is done (without a null pointer check), will result in an exception - I guess this is okay(?) Or should you test in your iterator and throw a ConcurrentModification?
I think it's simpler if you maintain an AtomicReference
in the node to the element and test that rather than a separate boolean and the reference.
Edit 1:
Looking at this:
// Doesn't matter if it fails. That would just mean someone else was doing the same.
head.set(freeNode.next);
Is this necessarily correct? If Thread A sets $n+1$ as head at the same time as Thread B setting n as head, and B succeeds - is the ring in a consistent state? I guess the no ordering guarantee means that this doesn't matter too much, but it would violate the $O(1)$ claim! ;) In fact, I would question the whole $O(1)$ claim, this is at best $O(1)$ and at worst $O(n)$...
edited 4 hours ago
esote
3,02611241
3,02611241
answered Jun 21 '12 at 13:03
NimNim
375139
375139
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in theContainer
throughout iteration time will be offered to theIterator
user. Anything that is added/removed to/from theContainer
during iteration may or may not be presented to the user.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
1
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false fromhasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that theNode
class ispublic
and exposes itsattach
anddetach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
I hold theAtomicBoolean free
in theNode
to allow for the above activity (instantdetach/attach
). I will investigate your idea to see how it affects the algorithm.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
2. (revisited) On further thought, your scenario may result innull
being returned by thenext
method but will not result in a breach of contract as the iteration will continue becausenext
is aNode
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time betweenhasNext
andnext
.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
3
$begingroup$
btw - I think the standard practice with Iterator is that you would callhasNext()
and if this is true, callnext()
to get the element, you should not invokehasNext()
within the call tonext()
,next()
should just return the element.
$endgroup$
– Nim
Jun 21 '12 at 14:37
|
show 3 more comments
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in theContainer
throughout iteration time will be offered to theIterator
user. Anything that is added/removed to/from theContainer
during iteration may or may not be presented to the user.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
1
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false fromhasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that theNode
class ispublic
and exposes itsattach
anddetach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
I hold theAtomicBoolean free
in theNode
to allow for the above activity (instantdetach/attach
). I will investigate your idea to see how it affects the algorithm.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
2. (revisited) On further thought, your scenario may result innull
being returned by thenext
method but will not result in a breach of contract as the iteration will continue becausenext
is aNode
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time betweenhasNext
andnext
.
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
3
$begingroup$
btw - I think the standard practice with Iterator is that you would callhasNext()
and if this is true, callnext()
to get the element, you should not invokehasNext()
within the call tonext()
,next()
should just return the element.
$endgroup$
– Nim
Jun 21 '12 at 14:37
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in the
Container
throughout iteration time will be offered to the Iterator
user. Anything that is added/removed to/from the Container
during iteration may or may not be presented to the user.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
$begingroup$
1. You are correct - no statement has been made about ordering and there are no guarantees in that respect. All that is guaranteed is that anything that is in the
Container
throughout iteration time will be offered to the Iterator
user. Anything that is added/removed to/from the Container
during iteration may or may not be presented to the user.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:06
1
1
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false from
hasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that the Node
class is public
and exposes its attach
and detach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
2. I see your concern. Very good catch. This scenario could then return false from
hasNext
and thus halt the iteration prematurely. I will fix that. A combined attach/detach would be an interesting detail. Note that the Node
class is public
and exposes its attach
and detach
methods so it is possible (encouraged) to replace the object without freeing the slot it occupies if that is desired.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:07
$begingroup$
I hold the
AtomicBoolean free
in the Node
to allow for the above activity (instant detach/attach
). I will investigate your idea to see how it affects the algorithm.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
I hold the
AtomicBoolean free
in the Node
to allow for the above activity (instant detach/attach
). I will investigate your idea to see how it affects the algorithm.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:11
$begingroup$
2. (revisited) On further thought, your scenario may result in
null
being returned by the next
method but will not result in a breach of contract as the iteration will continue because next
is a Node
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time between hasNext
and next
.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
$begingroup$
2. (revisited) On further thought, your scenario may result in
null
being returned by the next
method but will not result in a breach of contract as the iteration will continue because next
is a Node
not an element. My mistake. BTW - there is an even wider window for this issue because the element can be removed any time between hasNext
and next
.$endgroup$
– OldCurmudgeon
Jun 21 '12 at 14:18
3
3
$begingroup$
btw - I think the standard practice with Iterator is that you would call
hasNext()
and if this is true, call next()
to get the element, you should not invoke hasNext()
within the call to next()
, next()
should just return the element.$endgroup$
– Nim
Jun 21 '12 at 14:37
$begingroup$
btw - I think the standard practice with Iterator is that you would call
hasNext()
and if this is true, call next()
to get the element, you should not invoke hasNext()
within the call to next()
, next()
should just return the element.$endgroup$
– Nim
Jun 21 '12 at 14:37
|
show 3 more comments
$begingroup$
If thread A does Container.remove()
and then gets interrupted by thread B after the .detach()
but before the //Mark it free
, and thread B then iterates, it's going to get a ref to an item that's null
, which is probably Not What You Want.
$endgroup$
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return anull
value. In your case the null value will be skipped.
$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
add a comment |
$begingroup$
If thread A does Container.remove()
and then gets interrupted by thread B after the .detach()
but before the //Mark it free
, and thread B then iterates, it's going to get a ref to an item that's null
, which is probably Not What You Want.
$endgroup$
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return anull
value. In your case the null value will be skipped.
$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
add a comment |
$begingroup$
If thread A does Container.remove()
and then gets interrupted by thread B after the .detach()
but before the //Mark it free
, and thread B then iterates, it's going to get a ref to an item that's null
, which is probably Not What You Want.
$endgroup$
If thread A does Container.remove()
and then gets interrupted by thread B after the .detach()
but before the //Mark it free
, and thread B then iterates, it's going to get a ref to an item that's null
, which is probably Not What You Want.
answered Jun 22 '12 at 2:48
pjzpjz
2,191815
2,191815
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return anull
value. In your case the null value will be skipped.
$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
add a comment |
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return anull
value. In your case the null value will be skipped.
$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return a
null
value. In your case the null value will be skipped.$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
$begingroup$
Thanks for the feedback - That was the case in the original code but a recent change triggered by a similar scenario pointed out by @Nim means that the iterator will never return a
null
value. In your case the null value will be skipped.$endgroup$
– OldCurmudgeon
Jun 22 '12 at 8:15
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f12691%2fo1-lock-free-container%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
$begingroup$
The original post is here.
$endgroup$
– OldCurmudgeon
Jun 18 '12 at 13:38
$begingroup$
The declaration of
stop
variable seems to be missing.$endgroup$
– rahul
Jun 21 '12 at 16:26
$begingroup$
@blufox - my apologies - it should have read
limit
$endgroup$
– OldCurmudgeon
Jun 21 '12 at 18:13