Skip to content

Commit

Permalink
[FIXED JENKINS-18065] made ALLRM.entrySet() smarter
Browse files Browse the repository at this point in the history
(cherry picked from commit 54c0846)

Conflicts:
	changelog.html
  • Loading branch information
kohsuke authored and olivergondza committed Aug 1, 2014
1 parent 5797534 commit 632d7fe
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 11 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/util/RunList.java
Expand Up @@ -220,7 +220,7 @@ public String toString() {

/**
* Return only the most recent builds.
* <em>Warning:</em> this method mutates the original list and then returns it.
* <em>Warning:</em> this method mutates the original list and then returns it.AbstractLazyLoadRunMap
* @param n a count
* @return the n most recent builds
* @since 1.507
Expand Down
Expand Up @@ -109,6 +109,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer,R> i
* Updated atomically. Once set to this field, the index object may not be modified.
*/
private volatile Index index = new Index();
private LazyLoadRunMapEntrySet<R> entrySet = new LazyLoadRunMapEntrySet<R>(this);

/**
* Pair of two maps into a single class, so that the changes can be made visible atomically,
Expand Down Expand Up @@ -274,7 +275,7 @@ public boolean isEmpty() {
@Override
public Set<Entry<Integer, R>> entrySet() {
assert baseDirInitialized();
return Collections.unmodifiableSet(new BuildReferenceMapAdapter<R>(this,all()).entrySet());
return entrySet;
}

/**
Expand Down Expand Up @@ -348,7 +349,7 @@ public R get(Object key) {
}

public R get(int n) {
return search(n,Direction.EXACT);
return search(n, Direction.EXACT);
}

/**
Expand Down Expand Up @@ -543,15 +544,15 @@ public R getById(String id) {
}

public R getByNumber(int n) {
return search(n,Direction.EXACT);
return search(n, Direction.EXACT);
}

public R put(R value) {
return _put(value);
}

protected R _put(R value) {
return put(getNumberOf(value),value);
return put(getNumberOf(value), value);
}

@Override
Expand Down Expand Up @@ -612,7 +613,7 @@ public synchronized void putAll(Map<? extends Integer,? extends R> rhs) {
* @return
* fully populated map.
*/
private TreeMap<Integer,BuildReference<R>> all() {
/*package*/ TreeMap<Integer,BuildReference<R>> all() {
if (!fullyLoaded) {
synchronized (this) {
if (!fullyLoaded) {
Expand Down
111 changes: 111 additions & 0 deletions core/src/main/java/jenkins/model/lazy/LazyLoadRunMapEntrySet.java
@@ -0,0 +1,111 @@
package jenkins.model.lazy;

import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;

import java.util.AbstractMap.SimpleEntry;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;

/**
* Set that backs {@link AbstractLazyLoadRunMap#entrySet()}.
*
* @author Kohsuke Kawaguchi
*/
class LazyLoadRunMapEntrySet<R> extends AbstractSet<Entry<Integer,R>> {
private final AbstractLazyLoadRunMap<R> owner;

/**
* Lazily loaded all entries.
*/
private Set<Entry<Integer,R>> all;

LazyLoadRunMapEntrySet(AbstractLazyLoadRunMap<R> owner) {
this.owner = owner;
}

private synchronized Set<Entry<Integer,R>> all() {
if (all==null)
all = new BuildReferenceMapAdapter<R>(owner,owner.all()).entrySet();
return all;
}

@Override
public int size() {
return all().size();
}

@Override
public boolean isEmpty() {
return owner.newestBuild()==null;
}

@Override
public boolean contains(Object o) {
if (o instanceof Entry) {
Entry e = (Entry) o;
Object k = e.getKey();
if (k instanceof Integer) {
return owner.getByNumber((Integer)k).equals(e.getValue());
}
}
return false;
}

@Override
public Iterator<Entry<Integer,R>> iterator() {
return new Iterator<Entry<Integer,R>>() {
R last = null;
R next = owner.newestBuild();

public boolean hasNext() {
return next!=null;
}

public Entry<Integer,R> next() {
last = next;
if (last!=null) {
next = owner.search(owner.getNumberOf(last)-1, Direction.DESC);
} else
throw new NoSuchElementException();
return entryOf(last);
}

private Entry<Integer, R> entryOf(R r) {
return new SimpleEntry<Integer, R>(owner.getNumberOf(r),r);
}

public void remove() {
if (last==null)
throw new UnsupportedOperationException();
owner.removeValue(last);
}
};
}

@Override
public Object[] toArray() {
return all().toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return all().toArray(a);
}

@Override
public boolean add(Entry<Integer, R> integerREntry) {
throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
if (o instanceof Entry) {
Entry e = (Entry) o;
return owner.removeValue((R)e.getValue());
}
return false;
}
}
Expand Up @@ -32,13 +32,15 @@
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.logging.Level;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.Issue;

/**
* @author Kohsuke Kawaguchi
Expand Down Expand Up @@ -206,17 +208,17 @@ public void fastLookup() throws IOException {

a.get(1).asserts(1,"A");
assertNull(a.get(2));
a.get(3).asserts(3,"B");
a.get(3).asserts(3, "B");
assertNull(a.get(4));
a.get(5).asserts(5,"C");
a.get(5).asserts(5, "C");
}

@Test
public void fastSearch() throws IOException {
FakeMap a = localBuilder.addBoth(1, "A").addBoth(3, "B").addBoth(5, "C").addBoth(7, "D").make();

// we should be using the cache to find the entry efficiently
a.search(6, Direction.ASC).asserts(7,"D");
a.search(6, Direction.ASC).asserts(7, "D");
a.search(2, Direction.DESC).asserts(1, "A");
}

Expand All @@ -234,7 +236,7 @@ public void bogusCacheAndHiddenRealData() throws IOException {

@Test
public void bogusCache2() throws IOException {
FakeMap a = localBuilder.addBogusCache(1,3,"A").make();
FakeMap a = localBuilder.addBogusCache(1, 3, "A").make();
assertNull(a.get(1));
a.get(3).asserts(3,"A");
}
Expand Down Expand Up @@ -294,7 +296,7 @@ public void indexOutOfBounds() throws Exception {
.addUnloadable("F")
.addUnloadable("G")
.add(200,"H")
.add(201,"I");
.add(201, "I");
FakeMap map = f.make();

Build x = map.search(Integer.MAX_VALUE, Direction.DESC);
Expand Down Expand Up @@ -362,4 +364,63 @@ public void indexOutOfBounds() throws Exception {
assertFalse(iterator.hasNext());
}

@Issue("JENKINS-18065")
@Test
public void entrySetIterator() {
Iterator<Entry<Integer, Build>> itr = a.entrySet().iterator();

// iterator, when created fresh, shouldn't force loading everything
// this involves binary searching, so it can load several.
assertTrue(a.getLoadedBuilds().size() < 3);

// check if the first entry is legit
assertTrue(itr.hasNext());
Entry<Integer, Build> e = itr.next();
assertEquals((Integer)5,e.getKey());
e.getValue().asserts(5, "C");

// now that the first entry is returned, we expect there to be two loaded
assertTrue(a.getLoadedBuilds().size() < 3);

// check if the second entry is legit
assertTrue(itr.hasNext());
e = itr.next();
assertEquals((Integer)3, e.getKey());
e.getValue().asserts(3,"B");

// repeat the process for the third one
assertTrue(a.getLoadedBuilds().size() <= 3);

// check if the third entry is legit
assertTrue(itr.hasNext());
e = itr.next();
assertEquals((Integer) 1, e.getKey());
e.getValue().asserts(1,"A");

assertFalse(itr.hasNext());
assertEquals(3, a.getLoadedBuilds().size());
}

@Issue("JENKINS-18065")
@Test
public void entrySetEmpty() {
// entrySet().isEmpty() shouldn't cause full data load
assertFalse(a.entrySet().isEmpty());
assertTrue(a.getLoadedBuilds().size() < 3);
}

@Issue("JENKINS-18065")
@Test
public void entrySetSize() {
assertEquals(3, a.entrySet().size());
assertEquals(0, b.entrySet().size());
}

@Issue("JENKINS-18065")
@Test
public void entrySetContains() {
for (Entry<Integer, Build> e : a.entrySet()) {
assertTrue(a.entrySet().contains(e));
}
}
}

0 comments on commit 632d7fe

Please sign in to comment.