Merge lp:~camptocamp/carriers-deliveries/7.0-threaded-dispatch-label-generation into lp:~stock-logistic-core-editors/carriers-deliveries/7.0

Proposed by Yannick Vaucher @ Camptocamp
Status: Needs review
Proposed branch: lp:~camptocamp/carriers-deliveries/7.0-threaded-dispatch-label-generation
Merge into: lp:~stock-logistic-core-editors/carriers-deliveries/7.0
Prerequisite: lp:~camptocamp/carriers-deliveries/7.0-delivery_carrier_label_dispatch-output-file-yvr
Diff against target: 230 lines (+148/-27)
2 files modified
delivery_carrier_label_dispatch/__openerp__.py (+14/-1)
delivery_carrier_label_dispatch/wizard/generate_labels.py (+134/-26)
To merge this branch: bzr merge lp:~camptocamp/carriers-deliveries/7.0-threaded-dispatch-label-generation
Reviewer Review Type Date Requested Status
Nicolas Bessi - Camptocamp (community) Approve
Stock and Logistic Core Editors Pending
Review via email: mp+215184@code.launchpad.net

Commit message

delivery_carrier_label_dispatch - Improve efficiency of label generation by threading it. For instance for postlogistics, using serialized calls to the SOAP web service is a really slow as it would wait for a response before sending the next request.

Also improve exception feed back. And thus exception are non blocking for all label generation. You will only have to relaunch generation for failed ones.

To post a comment you must log in.
42. By Yannick Vaucher @ Camptocamp

removed conflict garbage

Revision history for this message
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote :
Download full text (4.4 KiB)

Hello,

Thank for the patch

Small PEP8 in manifest also noticed that
the title underline is not correct

[Link module] Carrier labels - Picking dispatch
==============================

It should go to the end of previous line

sys is imported but not used

remark
q_except can be a simpler data cohntainer like a list has they are thread safe.

the nested try catch structure here is strange

        try:
            try:
                picking_out_obj.generate_labels(
                    thread_cr, uid, [picking.id],
                    tracking_ids=tracking_ids,
                    context=context)
            except orm.except_orm as e:
                # add information on picking and pack in the exception
                picking_name = _('Picking: %s') % picking.name
                pack_num = _('Pack: %s') % pack.name if pack else ''
                raise orm.except_orm(
                    e.name,
                    _('%s %s - %s') % (picking_name, pack_num, e.value)) ...

Read more...

review: Needs Fixing
43. By Yannick Vaucher @ Camptocamp

Fix rst title in manifest

44. By Yannick Vaucher @ Camptocamp

remove unused import

45. By Yannick Vaucher @ Camptocamp

[PEP8]

46. By Yannick Vaucher @ Camptocamp

rewrite exception handling for cursor and orm exceptions on generate_labels

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

Thanks for the review.

I made the fixes.

I have also rewritten the exception handling, thought it does exactly the same thing. I have to use nested exception to ensure the rollback is played in anycase. Then I want to complete informations on the orm exceptions.

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

s/thought/though

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) :
47. By Yannick Vaucher @ Camptocamp

Specify Exception class to ensure catching right exceptions and avoid catching SystemExit and KeyboardInterupt exceptions

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

fixed except: -> except Exception:

Revision history for this message
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote :

LGTM

review: Approve
Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

Note to myself

This project is now hosted on https://github.com/OCA/carriers-deliveries. Please move your proposal there if you still want to merge it once fixed. This guide may help you https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub

Unmerged revisions

47. By Yannick Vaucher @ Camptocamp

Specify Exception class to ensure catching right exceptions and avoid catching SystemExit and KeyboardInterupt exceptions

46. By Yannick Vaucher @ Camptocamp

rewrite exception handling for cursor and orm exceptions on generate_labels

45. By Yannick Vaucher @ Camptocamp

[PEP8]

44. By Yannick Vaucher @ Camptocamp

remove unused import

43. By Yannick Vaucher @ Camptocamp

Fix rst title in manifest

42. By Yannick Vaucher @ Camptocamp

removed conflict garbage

41. By Yannick Vaucher @ Camptocamp

delivery_carrier_label_dispatch - Improve efficiency of label generation by threading it. For instance for postlogistics, using serialized calls to the SOAP web service is a really slow as it would wait for a response before sending the next request.

40. By Yannick Vaucher @ Camptocamp

merge lp:~camptocamp/carriers-deliveries/7.0-delivery_carrier_label_dispatch-output-file-yvr

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'delivery_carrier_label_dispatch/__openerp__.py'
--- delivery_carrier_label_dispatch/__openerp__.py 2014-06-13 14:52:12 +0000
+++ delivery_carrier_label_dispatch/__openerp__.py 2014-06-13 14:52:12 +0000
@@ -27,7 +27,7 @@
27 'depends': ['base_delivery_carrier_label', 'picking_dispatch'],27 'depends': ['base_delivery_carrier_label', 'picking_dispatch'],
28 'description': """28 'description': """
29[Link module] Carrier labels - Picking dispatch29[Link module] Carrier labels - Picking dispatch
30==============================30===============================================
3131
32This module adds a wizard on picking dispatch to generate the labels32This module adds a wizard on picking dispatch to generate the labels
33of the packs. The labels are merged in one PDF file.33of the packs. The labels are merged in one PDF file.
@@ -37,6 +37,19 @@
3737
38If you don't define your pack it will be considered a picking is a single pack.38If you don't define your pack it will be considered a picking is a single pack.
3939
40
41Tips
42----
43For picking dispatch with huge number of labels to generate your can add a
44number of worker with ir.config_parameter `shipping_label.num_workers` to
45parallelize the generation on multiple workers.
46This can be really useful for exemple using PostLogistics web service to
47gain speed.
48
49Be careful not to set too many worker as each one will need to instanciate
50a cursor on database and this could generate PoolErrors.
51A good choice would be to set it as `db_maxconn` / number_of_worker / 2
52
40Contributors53Contributors
41------------54------------
4255
4356
=== modified file 'delivery_carrier_label_dispatch/wizard/generate_labels.py'
--- delivery_carrier_label_dispatch/wizard/generate_labels.py 2014-06-13 14:52:12 +0000
+++ delivery_carrier_label_dispatch/wizard/generate_labels.py 2014-06-13 14:52:12 +0000
@@ -20,13 +20,20 @@
20##############################################################################20##############################################################################
21from operator import attrgetter21from operator import attrgetter
22from itertools import groupby22from itertools import groupby
23import Queue
24import threading
25import logging
2326
27from openerp import pooler
24from openerp.osv import orm, fields28from openerp.osv import orm, fields
25from openerp.tools.translate import _29from openerp.tools.translate import _
2630
27from ..pdf_utils import assemble_pdf31from ..pdf_utils import assemble_pdf
2832
2933
34_logger = logging.getLogger(__name__)
35
36
30class DeliveryCarrierLabelGenerate(orm.TransientModel):37class DeliveryCarrierLabelGenerate(orm.TransientModel):
3138
32 _name = 'delivery.carrier.label.generate'39 _name = 'delivery.carrier.label.generate'
@@ -85,37 +92,138 @@
85 return None92 return None
86 return label_obj.browse(cr, uid, label_id[0], context=context)93 return label_obj.browse(cr, uid, label_id[0], context=context)
8794
95 def _do_generate_labels(self, cr, uid, wizard, pack, picking,
96 label, context=None):
97 """ Generate a label in a thread safe context
98
99 Here we declare a specific cursor so do not launch
100 too many threads
101 """
102 picking_out_obj = self.pool['stock.picking.out']
103 # generate the label of the pack
104 tracking_ids = [pack.id] if pack else None
105 # create a cursor to be thread safe
106 thread_cr = pooler.get_db(cr.dbname).cursor()
107 try:
108 picking_out_obj.generate_labels(
109 thread_cr, uid, [picking.id],
110 tracking_ids=tracking_ids,
111 context=context)
112 thread_cr.commit()
113 except Exception:
114 thread_cr.rollback()
115 try:
116 raise
117 except orm.except_orm as e:
118 # add information on picking and pack in the exception
119 picking_name = _('Picking: %s') % picking.name
120 pack_num = _('Pack: %s') % pack.name if pack else ''
121 raise orm.except_orm(
122 e.name,
123 _('%s %s - %s') % (picking_name, pack_num, e.value))
124 finally:
125 thread_cr.close()
126
127 def worker(self, q, q_except):
128 """ A worker to generate labels
129
130 Takes data from queue q
131
132 And if the worker encounters errors, he will add them in
133 q_except queue
134 """
135 while not q.empty():
136 args, kwargs = q.get()
137 try:
138 self._do_generate_labels(*args, **kwargs)
139 except Exception as e:
140 q_except.put(e)
141 finally:
142 q.task_done()
143
144 def _get_num_workers(self, cr, uid, context=None):
145 """ Get number of worker parameter for labels generation
146
147 Optional ir.config_parameter is `shipping_label.num_workers`
148 """
149 param_obj = self.pool['ir.config_parameter']
150 num_workers = param_obj.get_param(cr, uid,
151 'shipping_label.num_workers')
152 if not num_workers:
153 return 1
154 return int(num_workers)
155
88 def _get_all_pdf(self, cr, uid, wizard, dispatch, context=None):156 def _get_all_pdf(self, cr, uid, wizard, dispatch, context=None):
157 q = Queue.Queue()
158 q_except = Queue.Queue()
159
160 # create the tasks to generate labels
89 for pack, moves, label in self._get_packs(cr, uid, wizard, dispatch,161 for pack, moves, label in self._get_packs(cr, uid, wizard, dispatch,
90 context=context):162 context=context):
91 if not label or wizard.generate_new_labels:163 if not label or wizard.generate_new_labels:
92 picking_out_obj = self.pool['stock.picking.out']
93 picking = moves[0].picking_id164 picking = moves[0].picking_id
94 # generate the label of the pack165 args = (cr, uid, wizard, pack, picking, label)
95 if pack:166 kwargs = {'context': context}
96 tracking_ids = [pack.id]167 task = (args, kwargs)
97 else:168 q.put(task)
98 tracking_ids = None169
99 try:170 # create few workers to parallelize label generation
100 picking_out_obj.generate_labels(171 num_workers = self._get_num_workers(cr, uid, context=context)
101 cr, uid, [picking.id],172 _logger.info('Starting %s workers to generate labels', num_workers)
102 tracking_ids=tracking_ids,173 for i in range(num_workers):
103 context=context)174 t = threading.Thread(target=self.worker, args=(q, q_except))
104 except orm.except_orm as e:175 t.daemon = True
105 picking_name = _('Picking: %s') % picking.name176 t.start()
106 pack_num = _('Pack: %s') % pack.name if pack else ''177
107 raise orm.except_orm(178 # wait for all tasks to be done
108 e.name,179 q.join()
109 _('%s %s - %s') % (picking_name, pack_num, e.value))180
110 if pack:181 # We will not create a partial PDF if some labels weren't
111 label = self._find_pack_label(cr, uid, wizard, pack,182 # generated thus we raise catched exceptions by the workers
112 context=context)183 # We will try to regroup all orm exception in one
113 else:184 if not q_except.empty():
114 label = self._find_picking_label(cr, uid, wizard, picking,185
115 context=context)186 error_count = {}
116 if not label:187 messages = []
117 continue # no label could be generated188 while not q_except.empty():
189 e = q_except.get()
190 if isinstance(e, orm.except_orm):
191 if not e.name in error_count:
192 error_count[e.name] = 1
193 else:
194 error_count[e.name] += 1
195 messages.append(e.value)
196 else:
197 # raise other exceptions like PoolError if
198 # too many cursor where created by workers
199 raise e
200 titles = []
201 for key, v in error_count.iteritems():
202 titles.append('%sx %s' % (v, key))
203
204 title = _('Errors while generating labels: ') + ' '.join(titles)
205 message = _('Some labels couldn\'t be generated. Please correct '
206 'following errors and generate labels again to create '
207 'the ones which failed.\n\n'
208 ) + '\n'.join(messages)
209 raise orm.except_orm(title, message)
210
211 # create a new cursor to be up to date with what was created by workers
212 join_cr = pooler.get_db(cr.dbname).cursor()
213 for pack, moves, label in self._get_packs(join_cr, uid,
214 wizard, dispatch,
215 context=context):
216 picking = moves[0].picking_id
217 if pack:
218 label = self._find_pack_label(join_cr, uid, wizard, pack,
219 context=context)
220 else:
221 label = self._find_picking_label(join_cr, uid, wizard, picking,
222 context=context)
223 if not label:
224 continue # no label could be generated
118 yield label225 yield label
226 join_cr.close()
119227
120 def action_generate_labels(self, cr, uid, ids, context=None):228 def action_generate_labels(self, cr, uid, ids, context=None):
121 """229 """
@@ -128,7 +236,7 @@
128 if not this.dispatch_ids:236 if not this.dispatch_ids:
129 raise orm.except_orm(_('Error'), _('No picking dispatch selected'))237 raise orm.except_orm(_('Error'), _('No picking dispatch selected'))
130238
131 attachment_obj = self.pool.get('ir.attachment')239 attachment_obj = self.pool['ir.attachment']
132240
133 for dispatch in this.dispatch_ids:241 for dispatch in this.dispatch_ids:
134 labels = self._get_all_pdf(cr, uid, this, dispatch,242 labels = self._get_all_pdf(cr, uid, this, dispatch,

Subscribers

People subscribed via source and target branches