summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/runqueue.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/runqueue.py')
-rw-r--r--bitbake/lib/bb/runqueue.py205
1 files changed, 137 insertions, 68 deletions
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
index bc7e18175d..80f3d3282f 100644
--- a/bitbake/lib/bb/runqueue.py
+++ b/bitbake/lib/bb/runqueue.py
@@ -14,6 +14,7 @@ import os
14import sys 14import sys
15import stat 15import stat
16import errno 16import errno
17import itertools
17import logging 18import logging
18import re 19import re
19import bb 20import bb
@@ -128,6 +129,7 @@ class RunQueueStats:
128# runQueue state machine 129# runQueue state machine
129runQueuePrepare = 2 130runQueuePrepare = 2
130runQueueSceneInit = 3 131runQueueSceneInit = 3
132runQueueDumpSigs = 4
131runQueueRunning = 6 133runQueueRunning = 6
132runQueueFailed = 7 134runQueueFailed = 7
133runQueueCleanUp = 8 135runQueueCleanUp = 8
@@ -475,7 +477,6 @@ class RunQueueData:
475 self.runtaskentries = {} 477 self.runtaskentries = {}
476 478
477 def runq_depends_names(self, ids): 479 def runq_depends_names(self, ids):
478 import re
479 ret = [] 480 ret = []
480 for id in ids: 481 for id in ids:
481 nam = os.path.basename(id) 482 nam = os.path.basename(id)
@@ -728,6 +729,8 @@ class RunQueueData:
728 if mc == frommc: 729 if mc == frommc:
729 fn = taskData[mcdep].build_targets[pn][0] 730 fn = taskData[mcdep].build_targets[pn][0]
730 newdep = '%s:%s' % (fn,deptask) 731 newdep = '%s:%s' % (fn,deptask)
732 if newdep not in taskData[mcdep].taskentries:
733 bb.fatal("Task mcdepends on non-existent task %s" % (newdep))
731 taskData[mc].taskentries[tid].tdepends.append(newdep) 734 taskData[mc].taskentries[tid].tdepends.append(newdep)
732 735
733 for mc in taskData: 736 for mc in taskData:
@@ -1273,27 +1276,41 @@ class RunQueueData:
1273 1276
1274 bb.parse.siggen.set_setscene_tasks(self.runq_setscene_tids) 1277 bb.parse.siggen.set_setscene_tasks(self.runq_setscene_tids)
1275 1278
1279 starttime = time.time()
1280 lasttime = starttime
1281
1276 # Iterate over the task list and call into the siggen code 1282 # Iterate over the task list and call into the siggen code
1277 dealtwith = set() 1283 dealtwith = set()
1278 todeal = set(self.runtaskentries) 1284 todeal = set(self.runtaskentries)
1279 while todeal: 1285 while todeal:
1286 ready = set()
1280 for tid in todeal.copy(): 1287 for tid in todeal.copy():
1281 if not (self.runtaskentries[tid].depends - dealtwith): 1288 if not (self.runtaskentries[tid].depends - dealtwith):
1282 dealtwith.add(tid) 1289 self.runtaskentries[tid].taskhash_deps = bb.parse.siggen.prep_taskhash(tid, self.runtaskentries[tid].depends, self.dataCaches)
1283 todeal.remove(tid) 1290 # get_taskhash for a given tid *must* be called before get_unihash* below
1284 self.prepare_task_hash(tid) 1291 self.runtaskentries[tid].hash = bb.parse.siggen.get_taskhash(tid, self.runtaskentries[tid].depends, self.dataCaches)
1285 bb.event.check_for_interrupts(self.cooker.data) 1292 ready.add(tid)
1293 unihashes = bb.parse.siggen.get_unihashes(ready)
1294 for tid in ready:
1295 dealtwith.add(tid)
1296 todeal.remove(tid)
1297 self.runtaskentries[tid].unihash = unihashes[tid]
1298
1299 bb.event.check_for_interrupts(self.cooker.data)
1300
1301 if time.time() > (lasttime + 30):
1302 lasttime = time.time()
1303 hashequiv_logger.verbose("Initial setup loop progress: %s of %s in %s" % (len(todeal), len(self.runtaskentries), lasttime - starttime))
1304
1305 endtime = time.time()
1306 if (endtime-starttime > 60):
1307 hashequiv_logger.verbose("Initial setup loop took: %s" % (endtime-starttime))
1286 1308
1287 bb.parse.siggen.writeout_file_checksum_cache() 1309 bb.parse.siggen.writeout_file_checksum_cache()
1288 1310
1289 #self.dump_data() 1311 #self.dump_data()
1290 return len(self.runtaskentries) 1312 return len(self.runtaskentries)
1291 1313
1292 def prepare_task_hash(self, tid):
1293 bb.parse.siggen.prep_taskhash(tid, self.runtaskentries[tid].depends, self.dataCaches)
1294 self.runtaskentries[tid].hash = bb.parse.siggen.get_taskhash(tid, self.runtaskentries[tid].depends, self.dataCaches)
1295 self.runtaskentries[tid].unihash = bb.parse.siggen.get_unihash(tid)
1296
1297 def dump_data(self): 1314 def dump_data(self):
1298 """ 1315 """
1299 Dump some debug information on the internal data structures 1316 Dump some debug information on the internal data structures
@@ -1574,14 +1591,19 @@ class RunQueue:
1574 self.rqdata.init_progress_reporter.next_stage() 1591 self.rqdata.init_progress_reporter.next_stage()
1575 self.rqexe = RunQueueExecute(self) 1592 self.rqexe = RunQueueExecute(self)
1576 1593
1577 dump = self.cooker.configuration.dump_signatures 1594 dumpsigs = self.cooker.configuration.dump_signatures
1578 if dump: 1595 if dumpsigs:
1579 self.rqdata.init_progress_reporter.finish() 1596 self.rqdata.init_progress_reporter.finish()
1580 if 'printdiff' in dump: 1597 if 'printdiff' in dumpsigs:
1581 invalidtasks = self.print_diffscenetasks() 1598 self.invalidtasks_dump = self.print_diffscenetasks()
1582 self.dump_signatures(dump) 1599 self.state = runQueueDumpSigs
1583 if 'printdiff' in dump: 1600
1584 self.write_diffscenetasks(invalidtasks) 1601 if self.state is runQueueDumpSigs:
1602 dumpsigs = self.cooker.configuration.dump_signatures
1603 retval = self.dump_signatures(dumpsigs)
1604 if retval is False:
1605 if 'printdiff' in dumpsigs:
1606 self.write_diffscenetasks(self.invalidtasks_dump)
1585 self.state = runQueueComplete 1607 self.state = runQueueComplete
1586 1608
1587 if self.state is runQueueSceneInit: 1609 if self.state is runQueueSceneInit:
@@ -1672,33 +1694,42 @@ class RunQueue:
1672 bb.parse.siggen.dump_sigtask(taskfn, taskname, dataCaches[mc].stamp[taskfn], True) 1694 bb.parse.siggen.dump_sigtask(taskfn, taskname, dataCaches[mc].stamp[taskfn], True)
1673 1695
1674 def dump_signatures(self, options): 1696 def dump_signatures(self, options):
1675 if bb.cooker.CookerFeatures.RECIPE_SIGGEN_INFO not in self.cooker.featureset: 1697 if not hasattr(self, "dumpsigs_launched"):
1676 bb.fatal("The dump signatures functionality needs the RECIPE_SIGGEN_INFO feature enabled") 1698 if bb.cooker.CookerFeatures.RECIPE_SIGGEN_INFO not in self.cooker.featureset:
1677 1699 bb.fatal("The dump signatures functionality needs the RECIPE_SIGGEN_INFO feature enabled")
1678 bb.note("Writing task signature files") 1700
1679 1701 bb.note("Writing task signature files")
1680 max_process = int(self.cfgData.getVar("BB_NUMBER_PARSE_THREADS") or os.cpu_count() or 1) 1702
1681 def chunkify(l, n): 1703 max_process = int(self.cfgData.getVar("BB_NUMBER_PARSE_THREADS") or os.cpu_count() or 1)
1682 return [l[i::n] for i in range(n)] 1704 def chunkify(l, n):
1683 tids = chunkify(list(self.rqdata.runtaskentries), max_process) 1705 return [l[i::n] for i in range(n)]
1684 # We cannot use the real multiprocessing.Pool easily due to some local data 1706 dumpsigs_tids = chunkify(list(self.rqdata.runtaskentries), max_process)
1685 # that can't be pickled. This is a cheap multi-process solution. 1707
1686 launched = [] 1708 # We cannot use the real multiprocessing.Pool easily due to some local data
1687 while tids: 1709 # that can't be pickled. This is a cheap multi-process solution.
1688 if len(launched) < max_process: 1710 self.dumpsigs_launched = []
1689 p = Process(target=self._rq_dump_sigtid, args=(tids.pop(), )) 1711
1712 for tids in dumpsigs_tids:
1713 p = Process(target=self._rq_dump_sigtid, args=(tids, ))
1690 p.start() 1714 p.start()
1691 launched.append(p) 1715 self.dumpsigs_launched.append(p)
1692 for q in launched: 1716
1693 # The finished processes are joined when calling is_alive() 1717 return 1.0
1694 if not q.is_alive(): 1718
1695 launched.remove(q) 1719 for q in self.dumpsigs_launched:
1696 for p in launched: 1720 # The finished processes are joined when calling is_alive()
1721 if not q.is_alive():
1722 self.dumpsigs_launched.remove(q)
1723
1724 if self.dumpsigs_launched:
1725 return 1.0
1726
1727 for p in self.dumpsigs_launched:
1697 p.join() 1728 p.join()
1698 1729
1699 bb.parse.siggen.dump_sigs(self.rqdata.dataCaches, options) 1730 bb.parse.siggen.dump_sigs(self.rqdata.dataCaches, options)
1700 1731
1701 return 1732 return False
1702 1733
1703 def print_diffscenetasks(self): 1734 def print_diffscenetasks(self):
1704 def get_root_invalid_tasks(task, taskdepends, valid, noexec, visited_invalid): 1735 def get_root_invalid_tasks(task, taskdepends, valid, noexec, visited_invalid):
@@ -2175,12 +2206,20 @@ class RunQueueExecute:
2175 if not hasattr(self, "sorted_setscene_tids"): 2206 if not hasattr(self, "sorted_setscene_tids"):
2176 # Don't want to sort this set every execution 2207 # Don't want to sort this set every execution
2177 self.sorted_setscene_tids = sorted(self.rqdata.runq_setscene_tids) 2208 self.sorted_setscene_tids = sorted(self.rqdata.runq_setscene_tids)
2209 # Resume looping where we left off when we returned to feed the mainloop
2210 self.setscene_tids_generator = itertools.cycle(self.rqdata.runq_setscene_tids)
2178 2211
2179 task = None 2212 task = None
2180 if not self.sqdone and self.can_start_task(): 2213 if not self.sqdone and self.can_start_task():
2181 # Find the next setscene to run 2214 loopcount = 0
2182 for nexttask in self.sorted_setscene_tids: 2215 # Find the next setscene to run, exit the loop when we've processed all tids or found something to execute
2216 while loopcount < len(self.rqdata.runq_setscene_tids):
2217 loopcount += 1
2218 nexttask = next(self.setscene_tids_generator)
2183 if nexttask in self.sq_buildable and nexttask not in self.sq_running and self.sqdata.stamps[nexttask] not in self.build_stamps.values() and nexttask not in self.sq_harddep_deferred: 2219 if nexttask in self.sq_buildable and nexttask not in self.sq_running and self.sqdata.stamps[nexttask] not in self.build_stamps.values() and nexttask not in self.sq_harddep_deferred:
2220 if nexttask in self.sq_deferred and self.sq_deferred[nexttask] not in self.runq_complete:
2221 # Skip deferred tasks quickly before the 'expensive' tests below - this is key to performant multiconfig builds
2222 continue
2184 if nexttask not in self.sqdata.unskippable and self.sqdata.sq_revdeps[nexttask] and \ 2223 if nexttask not in self.sqdata.unskippable and self.sqdata.sq_revdeps[nexttask] and \
2185 nexttask not in self.sq_needed_harddeps and \ 2224 nexttask not in self.sq_needed_harddeps and \
2186 self.sqdata.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and \ 2225 self.sqdata.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and \
@@ -2210,8 +2249,7 @@ class RunQueueExecute:
2210 if t in self.runq_running and t not in self.runq_complete: 2249 if t in self.runq_running and t not in self.runq_complete:
2211 continue 2250 continue
2212 if nexttask in self.sq_deferred: 2251 if nexttask in self.sq_deferred:
2213 if self.sq_deferred[nexttask] not in self.runq_complete: 2252 # Deferred tasks that were still deferred were skipped above so we now need to process
2214 continue
2215 logger.debug("Task %s no longer deferred" % nexttask) 2253 logger.debug("Task %s no longer deferred" % nexttask)
2216 del self.sq_deferred[nexttask] 2254 del self.sq_deferred[nexttask]
2217 valid = self.rq.validate_hashes(set([nexttask]), self.cooker.data, 0, False, summary=False) 2255 valid = self.rq.validate_hashes(set([nexttask]), self.cooker.data, 0, False, summary=False)
@@ -2438,14 +2476,17 @@ class RunQueueExecute:
2438 taskdepdata_cache = {} 2476 taskdepdata_cache = {}
2439 for task in self.rqdata.runtaskentries: 2477 for task in self.rqdata.runtaskentries:
2440 (mc, fn, taskname, taskfn) = split_tid_mcfn(task) 2478 (mc, fn, taskname, taskfn) = split_tid_mcfn(task)
2441 pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn] 2479 taskdepdata_cache[task] = bb.TaskData(
2442 deps = self.rqdata.runtaskentries[task].depends 2480 pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn],
2443 provides = self.rqdata.dataCaches[mc].fn_provides[taskfn] 2481 taskname = taskname,
2444 taskhash = self.rqdata.runtaskentries[task].hash 2482 fn = fn,
2445 unihash = self.rqdata.runtaskentries[task].unihash 2483 deps = self.filtermcdeps(task, mc, self.rqdata.runtaskentries[task].depends),
2446 deps = self.filtermcdeps(task, mc, deps) 2484 provides = self.rqdata.dataCaches[mc].fn_provides[taskfn],
2447 hashfn = self.rqdata.dataCaches[mc].hashfn[taskfn] 2485 taskhash = self.rqdata.runtaskentries[task].hash,
2448 taskdepdata_cache[task] = [pn, taskname, fn, deps, provides, taskhash, unihash, hashfn] 2486 unihash = self.rqdata.runtaskentries[task].unihash,
2487 hashfn = self.rqdata.dataCaches[mc].hashfn[taskfn],
2488 taskhash_deps = self.rqdata.runtaskentries[task].taskhash_deps,
2489 )
2449 2490
2450 self.taskdepdata_cache = taskdepdata_cache 2491 self.taskdepdata_cache = taskdepdata_cache
2451 2492
@@ -2460,9 +2501,11 @@ class RunQueueExecute:
2460 while next: 2501 while next:
2461 additional = [] 2502 additional = []
2462 for revdep in next: 2503 for revdep in next:
2463 self.taskdepdata_cache[revdep][6] = self.rqdata.runtaskentries[revdep].unihash 2504 self.taskdepdata_cache[revdep] = self.taskdepdata_cache[revdep]._replace(
2505 unihash=self.rqdata.runtaskentries[revdep].unihash
2506 )
2464 taskdepdata[revdep] = self.taskdepdata_cache[revdep] 2507 taskdepdata[revdep] = self.taskdepdata_cache[revdep]
2465 for revdep2 in self.taskdepdata_cache[revdep][3]: 2508 for revdep2 in self.taskdepdata_cache[revdep].deps:
2466 if revdep2 not in taskdepdata: 2509 if revdep2 not in taskdepdata:
2467 additional.append(revdep2) 2510 additional.append(revdep2)
2468 next = additional 2511 next = additional
@@ -2531,9 +2574,6 @@ class RunQueueExecute:
2531 self.rqdata.runtaskentries[hashtid].unihash = unihash 2574 self.rqdata.runtaskentries[hashtid].unihash = unihash
2532 bb.parse.siggen.set_unihash(hashtid, unihash) 2575 bb.parse.siggen.set_unihash(hashtid, unihash)
2533 toprocess.add(hashtid) 2576 toprocess.add(hashtid)
2534 if torehash:
2535 # Need to save after set_unihash above
2536 bb.parse.siggen.save_unitaskhashes()
2537 2577
2538 # Work out all tasks which depend upon these 2578 # Work out all tasks which depend upon these
2539 total = set() 2579 total = set()
@@ -2556,17 +2596,28 @@ class RunQueueExecute:
2556 elif self.rqdata.runtaskentries[p].depends.isdisjoint(total): 2596 elif self.rqdata.runtaskentries[p].depends.isdisjoint(total):
2557 next.add(p) 2597 next.add(p)
2558 2598
2599 starttime = time.time()
2600 lasttime = starttime
2601
2559 # When an item doesn't have dependencies in total, we can process it. Drop items from total when handled 2602 # When an item doesn't have dependencies in total, we can process it. Drop items from total when handled
2560 while next: 2603 while next:
2561 current = next.copy() 2604 current = next.copy()
2562 next = set() 2605 next = set()
2606 ready = {}
2563 for tid in current: 2607 for tid in current:
2564 if self.rqdata.runtaskentries[p].depends and not self.rqdata.runtaskentries[tid].depends.isdisjoint(total): 2608 if self.rqdata.runtaskentries[p].depends and not self.rqdata.runtaskentries[tid].depends.isdisjoint(total):
2565 continue 2609 continue
2610 # get_taskhash for a given tid *must* be called before get_unihash* below
2611 ready[tid] = bb.parse.siggen.get_taskhash(tid, self.rqdata.runtaskentries[tid].depends, self.rqdata.dataCaches)
2612
2613 unihashes = bb.parse.siggen.get_unihashes(ready.keys())
2614
2615 for tid in ready:
2566 orighash = self.rqdata.runtaskentries[tid].hash 2616 orighash = self.rqdata.runtaskentries[tid].hash
2567 newhash = bb.parse.siggen.get_taskhash(tid, self.rqdata.runtaskentries[tid].depends, self.rqdata.dataCaches) 2617 newhash = ready[tid]
2568 origuni = self.rqdata.runtaskentries[tid].unihash 2618 origuni = self.rqdata.runtaskentries[tid].unihash
2569 newuni = bb.parse.siggen.get_unihash(tid) 2619 newuni = unihashes[tid]
2620
2570 # FIXME, need to check it can come from sstate at all for determinism? 2621 # FIXME, need to check it can come from sstate at all for determinism?
2571 remapped = False 2622 remapped = False
2572 if newuni == origuni: 2623 if newuni == origuni:
@@ -2587,6 +2638,15 @@ class RunQueueExecute:
2587 next |= self.rqdata.runtaskentries[tid].revdeps 2638 next |= self.rqdata.runtaskentries[tid].revdeps
2588 total.remove(tid) 2639 total.remove(tid)
2589 next.intersection_update(total) 2640 next.intersection_update(total)
2641 bb.event.check_for_interrupts(self.cooker.data)
2642
2643 if time.time() > (lasttime + 30):
2644 lasttime = time.time()
2645 hashequiv_logger.verbose("Rehash loop slow progress: %s in %s" % (len(total), lasttime - starttime))
2646
2647 endtime = time.time()
2648 if (endtime-starttime > 60):
2649 hashequiv_logger.verbose("Rehash loop took more than 60s: %s" % (endtime-starttime))
2590 2650
2591 if changed: 2651 if changed:
2592 for mc in self.rq.worker: 2652 for mc in self.rq.worker:
@@ -2712,8 +2772,12 @@ class RunQueueExecute:
2712 logger.debug2("%s was unavailable and is a hard dependency of %s so skipping" % (task, dep)) 2772 logger.debug2("%s was unavailable and is a hard dependency of %s so skipping" % (task, dep))
2713 self.sq_task_failoutright(dep) 2773 self.sq_task_failoutright(dep)
2714 continue 2774 continue
2775
2776 # For performance, only compute allcovered once if needed
2777 if self.sqdata.sq_deps[task]:
2778 allcovered = self.scenequeue_covered | self.scenequeue_notcovered
2715 for dep in sorted(self.sqdata.sq_deps[task]): 2779 for dep in sorted(self.sqdata.sq_deps[task]):
2716 if self.sqdata.sq_revdeps[dep].issubset(self.scenequeue_covered | self.scenequeue_notcovered): 2780 if self.sqdata.sq_revdeps[dep].issubset(allcovered):
2717 if dep not in self.sq_buildable: 2781 if dep not in self.sq_buildable:
2718 self.sq_buildable.add(dep) 2782 self.sq_buildable.add(dep)
2719 2783
@@ -2806,13 +2870,19 @@ class RunQueueExecute:
2806 additional = [] 2870 additional = []
2807 for revdep in next: 2871 for revdep in next:
2808 (mc, fn, taskname, taskfn) = split_tid_mcfn(revdep) 2872 (mc, fn, taskname, taskfn) = split_tid_mcfn(revdep)
2809 pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn]
2810 deps = getsetscenedeps(revdep) 2873 deps = getsetscenedeps(revdep)
2811 provides = self.rqdata.dataCaches[mc].fn_provides[taskfn] 2874
2812 taskhash = self.rqdata.runtaskentries[revdep].hash 2875 taskdepdata[revdep] = bb.TaskData(
2813 unihash = self.rqdata.runtaskentries[revdep].unihash 2876 pn = self.rqdata.dataCaches[mc].pkg_fn[taskfn],
2814 hashfn = self.rqdata.dataCaches[mc].hashfn[taskfn] 2877 taskname = taskname,
2815 taskdepdata[revdep] = [pn, taskname, fn, deps, provides, taskhash, unihash, hashfn] 2878 fn = fn,
2879 deps = deps,
2880 provides = self.rqdata.dataCaches[mc].fn_provides[taskfn],
2881 taskhash = self.rqdata.runtaskentries[revdep].hash,
2882 unihash = self.rqdata.runtaskentries[revdep].unihash,
2883 hashfn = self.rqdata.dataCaches[mc].hashfn[taskfn],
2884 taskhash_deps = self.rqdata.runtaskentries[revdep].taskhash_deps,
2885 )
2816 for revdep2 in deps: 2886 for revdep2 in deps:
2817 if revdep2 not in taskdepdata: 2887 if revdep2 not in taskdepdata:
2818 additional.append(revdep2) 2888 additional.append(revdep2)
@@ -2964,14 +3034,13 @@ def build_scenequeue_data(sqdata, rqdata, sqrq):
2964 rqdata.init_progress_reporter.next_stage(len(rqdata.runtaskentries)) 3034 rqdata.init_progress_reporter.next_stage(len(rqdata.runtaskentries))
2965 3035
2966 # Sanity check all dependencies could be changed to setscene task references 3036 # Sanity check all dependencies could be changed to setscene task references
2967 for taskcounter, tid in enumerate(rqdata.runtaskentries): 3037 for tid in rqdata.runtaskentries:
2968 if tid in rqdata.runq_setscene_tids: 3038 if tid in rqdata.runq_setscene_tids:
2969 pass 3039 pass
2970 elif sq_revdeps_squash[tid]: 3040 elif sq_revdeps_squash[tid]:
2971 bb.msg.fatal("RunQueue", "Something went badly wrong during scenequeue generation, halting. Please report this problem.") 3041 bb.msg.fatal("RunQueue", "Something went badly wrong during scenequeue generation, halting. Please report this problem.")
2972 else: 3042 else:
2973 del sq_revdeps_squash[tid] 3043 del sq_revdeps_squash[tid]
2974 rqdata.init_progress_reporter.update(taskcounter)
2975 3044
2976 rqdata.init_progress_reporter.next_stage() 3045 rqdata.init_progress_reporter.next_stage()
2977 3046
@@ -3261,7 +3330,7 @@ class runQueuePipe():
3261 3330
3262 start = len(self.queue) 3331 start = len(self.queue)
3263 try: 3332 try:
3264 self.queue.extend(self.input.read(102400) or b"") 3333 self.queue.extend(self.input.read(512 * 1024) or b"")
3265 except (OSError, IOError) as e: 3334 except (OSError, IOError) as e:
3266 if e.errno != errno.EAGAIN: 3335 if e.errno != errno.EAGAIN:
3267 raise 3336 raise