1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *
19 */
20 package org.apache.directory.mavibot.btree;
21
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.TreeSet;
29
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33
34 /**
35 * A class used for reclaiming the copied pages.
36 *
37 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
38 */
39 public class PageReclaimer
40 {
41 /** the record manager */
42 private RecordManager rm;
43
44 /** The LoggerFactory used by this class */
45 protected static final Logger LOG = LoggerFactory.getLogger( PageReclaimer.class );
46
47 /** a flag to detect the running state */
48 private boolean running = false;
49
50 /**
51 * Creates a new instance of PageReclaimer.
52 *
53 * @param rm the record manager
54 */
55 public PageReclaimer( RecordManager rm )
56 {
57 this.rm = rm;
58 }
59
60
61 /**
62 * relcaims the copied pages
63 */
64 /* no qualifier */ void reclaim()
65 {
66 //System.out.println( "reclaiming pages" );
67 try
68 {
69 if ( running )
70 {
71 return;
72 }
73
74 running = true;
75
76 Set<String> managed = rm.getManagedTrees();
77
78 for ( String name : managed )
79 {
80 PersistedBTree tree = ( PersistedBTree ) rm.getManagedTree( name );
81
82 long latestRev = tree.getRevision();
83
84 Set<Long> inUseRevisions = new TreeSet<Long>();
85
86 // the tree might have been removed
87 if ( tree != null )
88 {
89 Iterator<ReadTransaction> txnItr = tree.getReadTransactions().iterator();
90 while ( txnItr.hasNext() )
91 {
92 inUseRevisions.add( txnItr.next().getRevision() );
93 }
94 }
95
96 List<RevisionOffset> copiedRevisions = getRevisions( name );
97
98 // the revision last removed from copiedPage BTree
99 long lastRemovedRev = -1;
100
101 List<Long> freeList = new ArrayList<Long>();
102
103 for ( RevisionOffset ro : copiedRevisions )
104 {
105 long rv = ro.getRevision();
106 if ( inUseRevisions.contains( rv ) )
107 {
108 //System.out.println( "Revision " + rv + " of BTree " + name + " is in use, not reclaiming pages" );
109 break;
110 }
111
112 long[] offsets = ro.getOffsets();
113
114 //System.out.println( "Reclaiming " + Arrays.toString( offsets ) + "( " + offsets.length + " ) pages of the revision " + rv + " of BTree " + name );
115
116 for( long l : offsets )
117 {
118 freeList.add( l );
119 }
120
121 RevisionName key = new RevisionName( rv, name );
122
123 //System.out.println( "delete cpb key " + key );
124 rm.copiedPageBtree.delete( key );
125 lastRemovedRev = rv;
126 }
127
128 // no new txn is needed for the operations on BoB
129 // and also no need to traverse BoB if the tree is a sub-btree
130 if ( ( lastRemovedRev != -1 ) && !tree.isAllowDuplicates() )
131 {
132 // we SHOULD NOT delete the latest revision from BoB
133 NameRevision nr = new NameRevision( name, latestRev );
134 TupleCursor<NameRevision, Long> cursor = rm.btreeOfBtrees.browseFrom( nr );
135
136 List<Long> btreeHeaderOffsets = new ArrayList<Long>();
137
138 while ( cursor.hasPrev() )
139 {
140 Tuple<NameRevision, Long> t = cursor.prev();
141 //System.out.println( "deleting BoB rev " + t.getKey() + " latest rev " + latestRev );
142 rm.btreeOfBtrees.delete( t.getKey() );
143 btreeHeaderOffsets.add( t.value );
144 }
145
146 cursor.close();
147
148 for( Long l : btreeHeaderOffsets )
149 {
150 // the offset may have already been present while
151 // clearing CPB so skip it here, otherwise it will result in OOM
152 // due to the attempt to free and already freed page
153 if(freeList.contains( l ))
154 {
155 //System.out.println( "bob duplicate offset " + l );
156 continue;
157 }
158
159 freeList.add( l );
160 }
161 }
162
163 for( Long offset : freeList )
164 {
165 PageIO[] pageIos = rm.readPageIOs( offset, -1L );
166
167 for ( PageIO pageIo : pageIos )
168 {
169 rm.free( pageIo );
170 }
171 }
172
173 }
174
175 running = false;
176 }
177 catch ( Exception e )
178 {
179 running = false;
180 rm.rollback();
181 LOG.warn( "Errors while reclaiming", e );
182 throw new RuntimeException( e );
183 }
184 }
185
186
187 /**
188 * gets a list of all the copied pages of a given B-Tree.
189 *
190 * @param name the name of the B-Tree
191 * @return list of RevisionOffset
192 * @throws Exception
193 */
194 private List<RevisionOffset> getRevisions( String name ) throws Exception
195 {
196 TupleCursor<RevisionName, long[]> cursor = rm.copiedPageBtree.browse();
197
198 List<RevisionOffset> lst = new ArrayList<RevisionOffset>();
199
200 while ( cursor.hasNext() )
201 {
202 Tuple<RevisionName, long[]> t = cursor.next();
203 RevisionName rn = t.getKey();
204 if ( name.equals( rn.getName() ) )
205 {
206 //System.out.println( t.getValue() );
207 lst.add( new RevisionOffset( rn.getRevision(), t.getValue() ) );
208 }
209 }
210
211 cursor.close();
212
213 return lst;
214 }
215 }