1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2009 Oracle. All rights reserved. 5 * 6 */ 7using System; 8using System.Collections.Generic; 9using System.IO; 10using System.Runtime.Serialization.Formatters.Binary; 11using System.Text; 12using System.Threading; 13 14using BerkeleyDB; 15 16namespace ex_txn { 17 public class ex_txn { 18 private DatabaseEnvironment env; 19 private Database db; 20 private Random generator = new Random(); 21 private bool inMem; 22 private string dbName, home; 23 private const int NUMTHREADS = 5; 24 25 public static void Main(string[] args) { 26 /* 27 * ex_txn is meant to be run from build_windows\AnyCPU, 28 * in either the Debug or Release directory. The 29 * required core libraries, however, are in either 30 * build_windows\Win32 or build_windows\x64, depending 31 * upon the platform. That location needs to be added 32 * to the PATH environment variable for the P/Invoke 33 * calls to work. 34 */ 35 try { 36 String pwd = Environment.CurrentDirectory; 37 pwd = Path.Combine(pwd, ".."); 38 pwd = Path.Combine(pwd, ".."); 39 pwd = Path.Combine(pwd, ".."); 40 pwd = Path.Combine(pwd, "build_windows"); 41 if (IntPtr.Size == 4) 42 pwd = Path.Combine(pwd, "Win32"); 43 else 44 pwd = Path.Combine(pwd, "x64"); 45#if DEBUG 46 pwd = Path.Combine(pwd, "Debug"); 47#else 48 pwd = Path.Combine(pwd, "Release"); 49#endif 50 pwd += ";" + Environment.GetEnvironmentVariable("PATH"); 51 Environment.SetEnvironmentVariable("PATH", pwd); 52 } catch (Exception e) { 53 Console.WriteLine( 54 "Unable to set the PATH environment variable."); 55 Console.WriteLine(e.Message); 56 return; 57 } 58 59 ex_txn obj = new ex_txn(); 60 obj.RunExample(args); 61 } 62 63 private void RunExample(string[] args) { 64 dbName = "ex_txn.db"; 65 home = "TESTDIR"; 66 67 if (!ParseArgs(args)) { 68 Usage(); 69 return; 70 } 71 72 try { 73 Open(); 74 75 // Start the threads. 76 Thread[] threadArray = new Thread[NUMTHREADS]; 77 for (int i = 0; i < NUMTHREADS; i++) { 78 threadArray[i] = new Thread( 79 new ThreadStart(WriteData)); 80 threadArray[i].Name = "Thread " + i; 81 threadArray[i].Start(); 82 } 83 84 for (int i = 0; i < NUMTHREADS; i++) { 85 threadArray[i].Join(); 86 Console.WriteLine("Thread " + i + " finished."); 87 } 88 89 } catch (DatabaseException e) { 90 Console.WriteLine("Caught exception: {0}", e.Message); 91 Console.WriteLine(e.StackTrace); 92 } finally { 93 Close(); 94 } 95 } 96 97 private void Open() { 98 Console.WriteLine("Opening environment and database"); 99 100 // Set up the environment. 101 DatabaseEnvironmentConfig envCfg = new DatabaseEnvironmentConfig(); 102 envCfg.Create = true; 103 envCfg.UseMPool = true; 104 envCfg.UseLocking = true; 105 envCfg.UseLogging = true; 106 envCfg.UseTxns = true; 107 108 // Allow multiple threads visit to the environment handle. 109 envCfg.FreeThreaded = true; 110 111 if (inMem) 112 envCfg.Private = true; 113 else 114 envCfg.RunRecovery = true; 115 116 /* 117 * Indicate that we want db to internally perform 118 * deadlock detection, aborting the transaction that 119 * has performed the least amount of WriteData activity 120 * in the event of a deadlock. 121 */ 122 envCfg.LockSystemCfg = new LockingConfig(); 123 envCfg.LockSystemCfg.DeadlockResolution = 124 DeadlockPolicy.MIN_WRITE; 125 126 if (inMem) { 127 // Specify in-memory logging. 128 envCfg.LogSystemCfg = new LogConfig(); 129 envCfg.LogSystemCfg.InMemory = true; 130 131 /* 132 * Specify the size of the in-memory log buffer 133 * Must be large enough to handle the log data 134 * created by the largest transaction. 135 */ 136 envCfg.LogSystemCfg.BufferSize = 10 * 1024 * 1024; 137 138 /* 139 * Specify the size of the in-memory cache, 140 * large enough to avoid paging to disk. 141 */ 142 envCfg.MPoolSystemCfg = new MPoolConfig(); 143 envCfg.MPoolSystemCfg.CacheSize = 144 new CacheInfo(0, 10 * 1024 * 1024, 1); 145 } 146 147 // Set up the database. 148 BTreeDatabaseConfig dbCfg = new BTreeDatabaseConfig(); 149 dbCfg.AutoCommit = true; 150 dbCfg.Creation = CreatePolicy.IF_NEEDED; 151 dbCfg.Duplicates = DuplicatesPolicy.SORTED; 152 dbCfg.FreeThreaded = true; 153 dbCfg.ReadUncommitted = true; 154 155 /* 156 * Open the environment. Any errors will be caught 157 * by the caller. 158 */ 159 env = DatabaseEnvironment.Open(home, envCfg); 160 161 /* 162 * Open the database. Do not provide a txn handle. This 163 * Open is autocommitted because BTreeDatabaseConfig.AutoCommit 164 * is true. 165 */ 166 dbCfg.Env = env; 167 db = BTreeDatabase.Open(dbName, dbCfg); 168 169 } 170 171 private void Close() { 172 Console.WriteLine("Closing environment and database"); 173 if (db != null) { 174 try { 175 db.Close(); 176 } catch (DatabaseException e) { 177 Console.WriteLine("Error closing db: " + e.ToString()); 178 Console.WriteLine(e.StackTrace); 179 } 180 } 181 182 if (env != null) { 183 try { 184 env.Close(); 185 } catch (DatabaseException e) { 186 Console.WriteLine("Error closing env: " + e.ToString()); 187 Console.WriteLine(e.StackTrace); 188 } 189 } 190 } 191 192 /* 193 * This simply counts the number of records contained in the 194 * database and returns the result. You can use this method 195 * in three ways: 196 * 197 * First call it with an active txn handle. 198 * Secondly, configure the cursor for dirty reads 199 * Third, call countRecords AFTER the writer has committed 200 * its transaction. 201 * 202 * If you do none of these things, the writer thread will 203 * self-deadlock. 204 * 205 * Note that this method exists only for illustrative purposes. 206 * A more straight-forward way to count the number of records in 207 * a database is to use the Database.getStats() method. 208 */ 209 private int CountRecords(Transaction txn) { 210 int count = 0; 211 Cursor cursor = null; 212 213 try { 214 // Get the cursor. 215 CursorConfig cc = new CursorConfig(); 216 217 /* 218 * Isolation degree one is ignored if the 219 * database was not opened for uncommitted 220 * read support. TxnGuide opens its database 221 * in this way and TxnGuideInMemory does not. 222 */ 223 cc.IsolationDegree = Isolation.DEGREE_ONE; 224 cursor = db.Cursor(cc, txn); 225 while (cursor.MoveNext()) 226 count++; 227 } finally { 228 if (cursor != null) 229 cursor.Close(); 230 } 231 232 return count; 233 } 234 235 private bool ParseArgs(string[] args) { 236 for (int i = 0; i < args.Length; i++) { 237 string s = args[i]; 238 if (s[0] != '-') { 239 Console.Error.WriteLine( 240 "Unrecognized option: " + args[i]); 241 return false; 242 } 243 244 switch (s[1]) { 245 case 'h': 246 home = args[++i]; 247 break; 248 case 'm': 249 inMem = true; 250 break; 251 default: 252 Console.Error.WriteLine( 253 "Unrecognized option: " + args[i]); 254 return false; 255 } 256 } 257 return true; 258 } 259 260 private void Usage() { 261 Console.WriteLine("ex_txn [-h <env directory>] [-m]"); 262 Console.WriteLine("\t -h home (Set environment directory.)"); 263 Console.WriteLine("\t -m (Run in memory, do not write to disk.)"); 264 } 265 266 private void WriteData() { 267 /* 268 * Write a series of records to the database using transaction 269 * protection. Deadlock handling is demonstrated here. 270 */ 271 BinaryFormatter formatter = new BinaryFormatter(); 272 MemoryStream ms = new MemoryStream(); 273 Random generator = new Random(); 274 Transaction txn = null; 275 276 string[] keys = {"key 1", "key 2", "key 3", "key 4", 277 "key 5", "key 6", "key 7", "key 8", "key 9", "key 10"}; 278 279 // Perform 20 transactions. 280 int iters = 0; 281 int retry_count = 0; 282 int maxRetry = 20; 283 while (iters < 50) { 284 try { 285 // Get a transaction. 286 txn = env.BeginTransaction(); 287 288 // Write 10 records to the db for each transaction. 289 for (int j = 0; j < 10; j++) { 290 // Get the key. 291 DatabaseEntry key; 292 key = new DatabaseEntry( 293 ASCIIEncoding.ASCII.GetBytes(keys[j])); 294 295 // Get the data. 296 PayloadData pd = new PayloadData( 297 iters + j, 298 Thread.CurrentThread.Name, 299 generator.NextDouble()); 300 301 formatter.Serialize(ms, pd); 302 Byte[] bytes = ms.GetBuffer(); 303 DatabaseEntry data = new DatabaseEntry(bytes); 304 305 // Put key/data pair within the transaction. 306 db.Put(key, data, txn); 307 } 308 309 // Commit the transaction. 310 Console.WriteLine("{0} committing txn: {1}", 311 Thread.CurrentThread.Name, iters); 312 313 int recCount = CountRecords(inMem ? txn : null); 314 Console.WriteLine("{0} found {1} records in the database.", 315 Thread.CurrentThread.Name, recCount); 316 317 try { 318 txn.Commit(); 319 txn = null; 320 } catch (DatabaseException e) { 321 Console.WriteLine( 322 "Error on txn commit: " + 323 e.ToString()); 324 } 325 326 iters++; 327 retry_count = 0; 328 } catch (DeadlockException) { 329 Console.WriteLine( 330 "##### {0} deadlocked.", Thread.CurrentThread.Name); 331 332 // Retry if necessary. 333 if (retry_count < maxRetry) { 334 Console.WriteLine("{0} retrying.", 335 Thread.CurrentThread.Name); 336 retry_count++; 337 } else { 338 Console.WriteLine("{0} out of retries. Giving up.", 339 Thread.CurrentThread.Name); 340 iters++; 341 retry_count = 0; 342 } 343 } catch (DatabaseException e) { 344 // Abort and don't retry. 345 iters++; 346 retry_count = 0; 347 Console.WriteLine(Thread.CurrentThread.Name + 348 " : caught exception: " + e.ToString()); 349 Console.WriteLine(Thread.CurrentThread.Name + 350 " : errno: " + e.ErrorCode); 351 Console.WriteLine(e.StackTrace); 352 } finally { 353 if (txn != null) { 354 try { 355 txn.Abort(); 356 } catch (DatabaseException e) { 357 Console.WriteLine("Error aborting transaction: " + 358 e.ToString()); 359 Console.WriteLine(e.StackTrace); 360 } 361 } 362 } 363 } 364 } 365 } 366} 367