Ah! I think I have the kernel of a usable idea here. Thanks.

How does this sound:  template 1 outputs template 2. Input to template 1 
kicks off the goroutines and places {{ get_results token_xyz }} as the 
output, but also has an output channel that we can wait on for all of the 
answers Then, template 2 is executed with a function that pulls the output 
from the channel. Sort of hard to explain, but seems on the face to be 
workable. I'll post results after I prototype it.

--
Michael

On Wednesday, May 31, 2017 at 1:23:40 PM UTC-5, Egon wrote:
>
> On Wednesday, 31 May 2017 20:34:56 UTC+3, Michael Brown wrote:
>>
>> This is almost certainly going to be a case where you have the correct 
>> solution and I don't have the go experience to properly understand it. I 
>> wasn't quite understanding how your code was "patching" the results and how 
>> the template package knows to wait for it. 
>>
>
> Let's say you have a template
>
> {{ sleep }} xxx {{ sleep }}
>
> Once you render with that approach you get something like this...
>
> <<token1>> xxx <<token2>>
>
> So instead of returning the actual value you replace them with a token. 
> And spawn a go routine to fetch the answers.
>
> You wait for the results or just some, doesn't matter.
>
> Once you get a results, you replace it in the buffer...
>
> token1 -> "alpha"
> token2 -> "beta"
>
> "alpha" xxx "beta"
>
> Or whatever the result is.
>
> However this approach is pretty limited when you want to range over the 
> results.
>
> Doing two passes over the template, might work better, but needs somewhat 
> better logic to handle lookup; but that could work for handling multiple 
> results...
>
> {{ sleep }}
>
> In the first pass you start the goroutines and each output goes into an 
> array of channels
>
> var rpcs = []func() interface{}
>
> var funcMap = template.FuncMap { "sleep": func() { rpcs = append(rpcs, 
> sleep)  },    }
>
> // now you schedule goroutines in whatever way you need for the rpcs and 
> write the results to a channel
> var results = []chan interface{}
>
> the second pass would do it as
> current := 0
>
> var funcMap = template.FuncMap { "sleep": func() interface{} {
>
>     result := <-results[current]
>
>     current++
>
>     return result
>
>
> // ...
>
>
> Without knowing the actual template, it is hard to recommend something 
> better simpler or easier.
>
> Let me describe my problem.
>>
>> I have a REST interface running on an embedded system. The REST api is 
>> mandated by another entity and I have zero control over the output, I have 
>> to produce specific conformant output. The data that I need to build the 
>> interface is on the system in a variety of other processes, and I can get 
>> that data via a couple of different RPC mechanisms, however sometimes these 
>> RPC mechanisms can be slow. (I'm in progress building go bindings for them).
>>
>> The current code which creates the REST interface is a huge morass of C 
>> code that generates the JSON output, so the exact structure of the JSON 
>> output is hardcoded in the code.
>>
>> I have an opportunity here, and that is that it appears to me that, with 
>> a little work, I can completely templatize the rest output. That is, I can 
>> have zero page-specific code and render almost all of the output by 
>> providing a small number of generic data access functions. The issue I ran 
>> into is the serial nature of the substitution kills the performance in my 
>> prototype.
>>
>> Yes, I can make a specific go function per page that gathers all the data 
>> I need and provides it to the template. But that would mean that I'd need 
>> to maintain both the templates and the data access functions. This is still 
>> an improvement on the old way of doing things, but I was hoping to jump 
>> straight to a fully templatized system, which according to my initial 
>> analysis, would be considerably less code and would not really be abusing 
>> the template system (most of the function calls are very straightforward 
>> and there is no heavy processing of the output).
>>
>> --
>> Michael
>>
>> On Wednesday, May 31, 2017 at 10:56:57 AM UTC-5, Egon wrote:
>>>
>>> Both of my described approaches run the funcs serially however it does 
>>> not wait for the response and later patxhes the results.
>>>
>>> Can you describe the whole thing you are building? Piecing the 
>>> requirements and purpose together from comments is difficult.
>>>
>>> I.e. how much memory, how big is request latency, who gets the output, 
>>> where do the templates come from...
>>>
>>> On May 31, 2017 6:40 PM, "Michael Brown" <michael...@gmail.com> wrote:
>>>
>>> The best thing I can think of is to modify text/template to add futures 
>>> support, and then do multi-pass rendering. The place to add this looks 
>>> relatively simple, however the implementation looks complicated (and I just 
>>> wrote my first program in go a couple weeks ago...)
>>>
>>> The problem that I see with the solution below is that text/template 
>>> execution tries to instantiate the values of each function serially and 
>>> essentially waits for completion on each, serially. I've spent the last 
>>> couple hours examining the text/template implementation.
>>> --
>>> Michael
>>>
>>>
>>> On Wednesday, May 31, 2017 at 8:59:20 AM UTC-5, Egon wrote:
>>>>
>>>> The best idea I can think of (without digging and modifying 
>>>> text/template) is to use special tokens and replace them afterwards...
>>>> Of course this approach has limitations in what it can do.
>>>>
>>>> *// note: code untested and incomplete*
>>>>
>>>> type Token string
>>>> type Queries struct {
>>>>     pending sync.WaitGroup
>>>>     mu sync.Mutex
>>>>     responses map[Token]string
>>>> }
>>>>
>>>> func (q *Queries) createToken() Token {
>>>>     return unique token
>>>> }
>>>>
>>>> func (q *Queries) Do(fn func() string) Token {
>>>>     token := q.createToken()
>>>>     q.pending.Add(1)
>>>>     go func(){
>>>>         defer q.pending.Done()
>>>>         result := fn()
>>>>         q.mu.Lock()
>>>>         q.responses[token] = result
>>>>         q.mu.Unlock()
>>>>     }()
>>>>     return token
>>>> }
>>>>
>>>> func (q *Queries) Wait(){ q.pending.Wait() }
>>>> func (q *Queries) Patch(data []byte) []byte {
>>>>     // replace tokens with responess
>>>> }
>>>>
>>>> func main() {
>>>>
>>>>    q := NewQueries()
>>>>
>>>>     var funcMap = template.FuncMap {
>>>>
>>>>         "sleep": func() Token { return q.Do(func() string { 
>>>> time.Sleep(1 * time.Second); return "slept" }) },
>>>>
>>>>     }
>>>>     tmpl, _ := template.New("test").Funcs(funcMap).Parse("{{sleep}} 
>>>> {{sleep}} {{sleep}}")
>>>>     var buf bytes.Buffer
>>>>
>>>>     tmpl.Execute(&buf, nil)
>>>>
>>>>     q.Wait()
>>>>
>>>>     os.Stdout.Write(q.Patch(buf.Bytes))
>>>>
>>>> }
>>>>
>>>> The other approach would be to do multiple passes:
>>>>
>>>> 1. execute template
>>>> 2. collect funcs that haven't been run yet
>>>> 2.1. no funcs left --> output
>>>> 3. execute these funcs, cache the func values
>>>> 4. goto step 1 using the cache
>>>>
>>>> On Wednesday, 31 May 2017 16:26:15 UTC+3, Michael Brown wrote:
>>>>>
>>>>> I am designing a system that will heavily use text/template processing 
>>>>> and I've run into one issue that is going to be a show stopper for me if 
>>>>> I 
>>>>> can't figure out a way around it.
>>>>>
>>>>> Execute() on a template will run all of the functions in the template 
>>>>> serially. 
>>>>>
>>>>> For example, when you run the code below, you can see it output 
>>>>> "slept" once every second until it completes after 3 seconds. In this 
>>>>> example, the sleep is simulating an RPC call to another process that may 
>>>>> take some considerable time (few tenths of a second), but there will be a 
>>>>> large number of these calls that could all theoretically run in parallel 
>>>>> (ie. there are no data dependencies between them). I'd really like to 
>>>>> know 
>>>>> a way that I could have the templating engine run all of the functions at 
>>>>> once and collect the output, ie. in the example below, the entire program 
>>>>> should run in 1 second.
>>>>>
>>>>> package main
>>>>>
>>>>>
>>>>> import (
>>>>>
>>>>>     "text/template"
>>>>>
>>>>>     "os"
>>>>>
>>>>>     "time"
>>>>>
>>>>> )
>>>>>
>>>>>
>>>>> var funcMap = template.FuncMap {
>>>>>
>>>>>     "sleep": func() string { time.Sleep(1 * time.Second); return 
>>>>> "slept" },
>>>>>
>>>>> }
>>>>>
>>>>>
>>>>> func main() {
>>>>>
>>>>>     tmpl, _ := template.New("test").Funcs(funcMap).Parse("{{sleep}} 
>>>>> {{sleep}} {{sleep}}")
>>>>>
>>>>>     tmpl.Execute(os.Stdout, nil)
>>>>>
>>>>> }
>>>>>
>>>>>
>>>>> -- 
>>> You received this message because you are subscribed to a topic in the 
>>> Google Groups "golang-nuts" group.
>>> To unsubscribe from this topic, visit 
>>> https://groups.google.com/d/topic/golang-nuts/j0WwjQE11rw/unsubscribe.
>>> To unsubscribe from this group and all its topics, send an email to 
>>> golang-nuts...@googlegroups.com.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
>>>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to