{"action":"created","comment":{"url":"https://api.github.com/repos/taskgraph/taskgraph/pulls/comments/27768327","id":27768327,"diff_hunk":"@@ -31,168 +33,516 @@ Topology: the topology is different from task to task. Each task will consider i\n and all others children.\n */\n \n-// bwmfData is used to carry indexes and values associated with each index. Index here\n-// can be row or column id, and value can be K wide, one for each topic;\n-type bwmfData struct {\n-\tIndex  int\n-\tValues []float64\n-}\n-\n-type Shard []*bwmfData\n-\n-func newDShard(index uint64) []*bwmfData {\n-\tpanic(\"\")\n-}\n-func newTShard(index uint64) []*bwmfData {\n-\tpanic(\"\")\n-}\n-\n-func (shard *Shard) randomFillValue() {\n-}\n-\n-type sparseVec struct {\n-\tIndexes []int\n-\tValues  []float64\n-}\n-\n // bwmfTasks holds two shards of original matrices (row and column), one shard of D,\n // and one shard of T. It works differently for odd and even epoch:\n // During odd epoch, 1. it fetch all T from other slaves, and finding better value for\n // local shard of D; 2. after it is done, it let every one knows. Vice versa for even epoch.\n // Task 0 will monitor the progress and responsible for starting the work of new epoch.\n type bwmfTask struct {\n-\tframework  taskgraph.Framework\n-\tepoch      uint64\n-\ttaskID     uint64\n-\tlogger     *log.Logger\n-\tnumOfTasks uint64\n+\tframework taskgraph.Framework\n+\tepoch     uint64\n+\ttaskID    uint64\n \n-\t// The original data.\n-\trowShard, columnShard []sparseVec\n+\tworkPath  string\n+\tlogger    *log.Logger\n+\tlogWriter *bufio.Writer\n \n-\tdtReady       *common.CountdownLatch\n-\tchildrenReady map[uint64]bool\n+\tnumOfTasks uint64\n+\tnumOfIters uint64\n+\tcurIter    uint64\n \n-\tdShard, tShard Shard\n-\td, t           []*bwmfData\n+\tfromOthers map[uint64]bool\n+\n+\t// The original data.\n+\trowBuf, columnBuf     []byte\n+\trowShard, columnShard *pb.SparseMatrixShard\n+\n+\tshardedD, shardedT *pb.DenseMatrixShard\n+\tfullD, fullT       map[uint32]*pb.DenseMatrixShard // blockId to Shard\n+\n+\t// shardedD is size of m*k, t shard is size of k*n (but still its layed-out as n*k)\n+\t// fullD is size of M*k, fullT is size of k*N (dittu, layout N*k)\n+\tm, n, k, M, N int\n+\tblockId       uint32\n+\n+\t// parameters for projected gradient methods\n+\tsigma, alpha, beta, tol float32\n+\n+\t// objective function, parameters and minimizer to solve bwmf\n+\ttLoss, dLoss *KLDivLoss\n+\toptimizer    *taskgraph_op.ProjectedGradient\n+\tstopCriteria taskgraph_op.StopCriteria\n+\n+\t// channels\n+\tepochChange chan *event\n+\tgetD        chan *event\n+\tgetT        chan *event\n+\tdataReady   chan *event\n+\tmetaReady   chan *event\n+\tupdateDone  chan *event\n+\texitChan    chan struct{}\n }\n \n // These two function carry out actual optimization.\n-func (t *bwmfTask) updateDShard() {}\n-func (t *bwmfTask) updateTShard() {}\n-\n-// Initialization: We need to read row and column shards of A.\n-func (t *bwmfTask) readShardsFromDisk() {}\n+func (bt *bwmfTask) updateDShard(ctx context.Context) {\n+\t// fullT is ready here\n+\t// if dLoss is not initialized\n+\tif bt.dLoss.n == -1 {\n+\t\tbt.dLoss.n = 0\n+\t\tfor _, m := range bt.fullT {\n+\t\t\tbt.dLoss.n += len(m.Row)\n+\t\t}\n+\t\tbt.N = bt.dLoss.n\n+\t\twh := make([][]float32, bt.N)\n+\t\tfor i, _ := range wh {\n+\t\t\twh[i] = make([]float32, bt.m)\n+\t\t}\n+\t\tbt.dLoss.WH = wh\n+\t}\n+\tbt.dLoss.W = NewBlocksParameter(&bt.fullT)\n \n-// Read dShard and tShard from last checkpoint if any.\n-func (t *bwmfTask) readLastCheckpoint() bool {\n-\tpanic(\"\")\n-}\n+\tparam := NewSingleBlockParameter(bt.shardedD)\n \n-// Task have all the data, compute local optimization of D/T.\n-func (t *bwmfTask) localCompute() {}\n-\n-// This is useful to bring the task up to speed from scratch or if it recovers.\n-func (t *bwmfTask) Init(taskID uint64, framework taskgraph.Framework) {\n-\tt.taskID = taskID\n-\tt.framework = framework\n-\tt.logger = log.New(os.Stdout, \"\", log.Ldate|log.Ltime|log.Lshortfile)\n-\t// Use some unique identifier to set Index \"in the future\".\n-\t// We can use taskID now.\n-\tt.dShard = newDShard(t.taskID)\n-\tt.tShard = newTShard(t.taskID)\n-\tt.readShardsFromDisk()\n-\tok := t.readLastCheckpoint()\n+\t// all set. Start the optimizing routine.\n+\tok := bt.optimizer.Minimize(bt.dLoss, bt.stopCriteria, param)\n \tif !ok {\n-\t\tt.dShard.randomFillValue()\n-\t\tt.tShard.randomFillValue()\n+\t\t// TODO report error\n \t}\n \n-\t// At initialization:\n-\t// Task 0 will start the iterations.\n+\t// signal that dShard has already been evaluated\n+\tbt.updateDone <- &event{ctx: ctx, epoch: bt.epoch}\n }\n \n-func (t *bwmfTask) Exit() {}\n+func (bt *bwmfTask) updateTShard(ctx context.Context) {\n+\t// Symmetric as updateDShard\n+\tif bt.tLoss.m == -1 {\n+\t\tbt.tLoss.m = 0\n+\t\tfor _, m := range bt.fullD {\n+\t\t\tbt.tLoss.m += len(m.Row)\n+\t\t}\n+\t\tbt.M = bt.tLoss.m\n+\t\twh := make([][]float32, bt.M)\n+\t\tfor i, _ := range wh {\n+\t\t\twh[i] = make([]float32, bt.n)\n+\t\t}\n+\t\tbt.tLoss.WH = wh\n+\t}\n+\tbt.tLoss.W = NewBlocksParameter(&bt.fullD)\n+\tparam := NewSingleBlockParameter(bt.shardedT)\n+\tok := bt.optimizer.Minimize(bt.tLoss, bt.stopCriteria, param)\n+\tif !ok {\n+\t\t// TODO report error\n+\t}\n+\tbt.updateDone <- &event{ctx: ctx, epoch: bt.epoch}\n+}\n \n-func (t *bwmfTask) SetEpoch(ctx taskgraph.Context, epoch uint64) {\n-\tt.logger.Printf(\"slave SetEpoch, task: %d, epoch: %d\\n\", t.taskID, epoch)\n-\tt.epoch = epoch\n-\tt.childrenReady = make(map[uint64]bool)\n-\tt.dtReady = common.NewCountdownLatch(int(t.numOfTasks))\n+// Dumping the temporary results.\n+func (bt *bwmfTask) checkpoint(epoch uint64) {\n+\tvar (\n+\t\tbuf      []byte\n+\t\terr      error\n+\t\tfilename string\n+\t)\n+\n+\t// In even epoch, we fetch D and update T; in odd epoch, we fetch T and update D.\n+\tif epoch%2 == 0 {\n+\t\tfilename = \"./shard.t.\" + strconv.FormatInt(int64(bt.taskID), 10) + \"-\" + strconv.FormatInt(int64(epoch), 10)\n+\t\tbuf, err = proto.Marshal(bt.shardedT)\n+\t} else {\n+\t\tfilename = \"./shard.d.\" + strconv.FormatInt(int64(bt.taskID), 10) + \"-\" + strconv.FormatInt(int64(epoch), 10)\n+\t\tbuf, err = proto.Marshal(bt.shardedD)\n+\t}\n+\tif err != nil {\n+\t\tbt.logger.Panicf(\"Failed marshalling result at epoch %d in task %d with err %s\", epoch, bt.taskID, err)\n+\t}\n+\t// write the buffer\n+\twriter, openErr := filesystem.NewLocalFSClient().OpenWriteCloser(filename)\n+\tif openErr != nil {\n+\t\tbt.logger.Panicf(\"Failed open %s with error %s.\", filename, openErr)\n+\t}\n+\t_, wErr := writer.Write(buf)\n+\tif wErr != nil {\n+\t\tbt.logger.Panicf(\"Failed writing to %s with err %s\", filename, wErr)\n+\t}\n \n-\t// Afterwards:\n-\t// We need to get all D/T from last epoch so that we can carry out local\n-\t// update on T/D.\n-\t// Even epochs: Fix D, calculate T;\n-\t// Odd epochs: Fix T, calculate D;\n+\t// remove the older instance\n+\tif int64(epoch)-2 >= 0 {\n+\t\t// TODO: No `Remove` interface for now.\n+\t\t// preEpoch := epoch - 2\n+\t}\n+}\n \n-\tif t.epoch%2 == 0 {\n-\t\tfor index := uint64(0); index < t.numOfTasks; index++ {\n-\t\t\tctx.DataRequest(index, \"getD\")\n-\t\t}\n+// load the dumped result from checkpoint.\n+// TODO: failure recovery using this func\n+func (bt *bwmfTask) recoverFromCheckpoint(epoch uint64) {\n+\tvar (\n+\t\tfilename string\n+\t\tshard    *pb.DenseMatrixShard\n+\t)\n+\n+\t// In even epoch, we fetch D and update T; in odd epoch, we fetch T and update D.\n+\tif epoch%2 == 0 {\n+\t\tfilename = \"./shard.t.checkpoint.\" + strconv.FormatInt(int64(bt.taskID), 10) + \"-\" + strconv.FormatInt(int64(epoch), 10)\n+\t\tshard = bt.shardedT\n \t} else {\n-\t\tfor index := uint64(0); index < t.numOfTasks; index++ {\n-\t\t\tctx.DataRequest(index, \"getT\")\n-\t\t}\n+\t\tfilename = \"./shard.d.checkpoint.\" + strconv.FormatInt(int64(bt.taskID), 10) + \"-\" + strconv.FormatInt(int64(epoch), 10)\n+\t\tshard = bt.shardedD\n+\t}\n+\n+\t// load the buffer\n+\tclient := NewLocalBufLoader()\n+\tclient.Init()\n+\tbuf, err := client.ReadAll(filename)\n+\tif err != nil {\n+\t\tbt.logger.Panicf(\"Failed loading file %s\", filename)\n \t}\n+\terr = proto.Unmarshal(buf, shard)\n+\tif err != nil {\n+\t\tbt.logger.Panicf(\"Failed unmarshalling result at epoch %d in task %d\", epoch, bt.taskID)\n+\t}\n+}\n+\n+func (bt *bwmfTask) Init(taskID uint64, framework taskgraph.Framework) {\n+\tbt.taskID = taskID\n+\tbt.framework = framework\n+\tbt.curIter = 0\n \n+\t// XXX This is for exposing the io.Writer for us to constantly flush.\n+\tlogFile, lfErr := os.Create(bt.workPath + \"/task-\" + strconv.FormatInt(int64(taskID), 10) + \".log\")\n+\tif lfErr != nil {\n+\t\tpanic(\"Failed creating logger.\")\n+\t}\n+\tbt.logWriter = bufio.NewWriter(logFile)\n+\tbt.logger = log.New(bt.logWriter, \"\", log.Ldate|log.Ltime|log.Lshortfile)\n+\tbt.logger.Println(\"Start logging.\")\n \tgo func() {\n-\t\t// Wait for all shards (either D or T, depending on the epoch) to be ready.\n-\t\tt.dtReady.Await()\n-\t\t// We can compute local shard result from A and D/T.\n-\t\tt.localCompute()\n-\t\t// Notify task 0 about the result.\n-\t\tctx.FlagMetaToParent(\"computed\")\n+\t\t// constantly flushing the log messages.\n+\t\tfor {\n+\t\t\tbt.logWriter.Flush()\n+\t\t\ttime.Sleep(1 * time.Millisecond)\n+\t\t}\n \t}()\n-}\n \n-func (t *bwmfTask) ParentMetaReady(ctx taskgraph.Context, parentID uint64, meta string) {}\n+\t// just for mem allocation\n+\tbt.rowShard = &pb.SparseMatrixShard{}\n+\tbt.columnShard = &pb.SparseMatrixShard{}\n+\trowUnmashalErr := proto.Unmarshal(bt.rowBuf, bt.rowShard)\n+\tcolumnUnmashalErr := proto.Unmarshal(bt.columnBuf, bt.columnShard)\n+\n+\tif rowUnmashalErr != nil {\n+\t\tbt.logger.Fatalf(\"Failed unmarshalling row shard data on task: %d\", bt.taskID)\n+\t}\n+\tif columnUnmashalErr != nil {\n+\t\tbt.logger.Fatalf(\"Failed unmarshalling column shard data on task: %d\", bt.taskID)\n+\t}\n+\n+\t// XXX(baigang) We set M and N via collecting all sharded D and T later.\n+\t// XXX(baigang) K is set in the task builder.\n+\tbt.m = len(bt.rowShard.Row)\n+\tbt.n = len(bt.columnShard.Row)\n \n-func (t *bwmfTask) ParentDataReady(ctx taskgraph.Context, parentID uint64, req string, resp []byte) {}\n+\tbt.shardedD = &pb.DenseMatrixShard{\n+\t\tRow: make([]*pb.DenseMatrixShard_DenseRow, bt.m),\n+\t}\n+\tbt.shardedT = &pb.DenseMatrixShard{\n+\t\tRow: make([]*pb.DenseMatrixShard_DenseRow, bt.n),\n+\t}\n+\tfor i, _ := range bt.shardedD.Row {\n+\t\tbt.shardedD.Row[i] = &pb.DenseMatrixShard_DenseRow{At: make([]float32, bt.k)}\n+\t\tfor j, _ := range bt.shardedD.Row[i].At {\n+\t\t\tbt.shardedD.Row[i].At[j] = rand.Float32()\n+\t\t}\n+\t}\n+\tfor i, _ := range bt.shardedT.Row {\n+\t\tbt.shardedT.Row[i] = &pb.DenseMatrixShard_DenseRow{At: make([]float32, bt.k)}\n+\t\tfor j, _ := range bt.shardedT.Row[i].At {\n+\t\t\tbt.shardedT.Row[i].At[j] = rand.Float32()\n+\t\t}\n+\t}\n \n-func (t *bwmfTask) ChildMetaReady(ctx taskgraph.Context, childID uint64, meta string) {\n-\t// Task zero should maintain the barrier for iterations.\n-\tif meta == \"computed\" {\n-\t\tt.childrenReady[childID] = true\n+\t// TODO set tLoss and dLoss\n+\tbt.tLoss = &KLDivLoss{\n+\t\tV:      bt.columnShard,\n+\t\tWH:     nil,\n+\t\tW:      nil,\n+\t\tm:      -1, // should be bt.M\n+\t\tn:      bt.n,\n+\t\tk:      bt.k,\n+\t\tsmooth: 1e-4,\n+\t\tlogger: bt.logger,\n \t}\n-\tif uint64(len(t.childrenReady)) < t.numOfTasks {\n-\t\treturn\n+\tbt.dLoss = &KLDivLoss{\n+\t\tV:      bt.rowShard,\n+\t\tWH:     nil,\n+\t\tW:      nil,\n+\t\tm:      bt.m,\n+\t\tn:      -1, // should be bt.N\n+\t\tk:      bt.k,\n+\t\tsmooth: 1e-4,\n+\t\tlogger: bt.logger,\n+\t}\n+\n+\tbt.stopCriteria = taskgraph_op.MakeFixCountStopCriteria(int(bt.numOfIters))\n+\tproj_len := 0\n+\tif bt.m > bt.n {\n+\t\tproj_len = bt.m * bt.k\n+\t} else {\n+\t\tproj_len = bt.n * bt.k\n \t}\n-\t// if we have all data, start next iteration.\n-\tctx.IncEpoch()\n-}\n \n-// Other nodes has served with their local shards.\n-func (t *bwmfTask) ChildDataReady(ctx taskgraph.Context, childID uint64, req string, resp []byte) {\n-\tt.dtReady.CountDown()\n+\tbt.optimizer = taskgraph_op.NewProjectedGradient(\n+\t\ttaskgraph_op.NewProjection(\n+\t\t\ttaskgraph_op.NewAllTheSameParameter(1e20, proj_len),\n+\t\t\ttaskgraph_op.NewAllTheSameParameter(1e-9, proj_len),\n+\t\t),\n+\t\tbt.beta,\n+\t\tbt.sigma,\n+\t\tbt.alpha,\n+\t)\n+\n+\t// channels for event-based `run()`\n+\tbt.epochChange = make(chan *event, 1)\n+\tbt.getD = make(chan *event, 1)\n+\tbt.getT = make(chan *event, 1)\n+\tbt.dataReady = make(chan *event, 1)\n+\tbt.updateDone = make(chan *event, 1)\n+\tbt.exitChan = make(chan struct{})\n+\n+\tbt.logger.Println(\"RowShard:\", *bt.rowShard)\n+\tbt.logger.Println(\"ColumnShard:\", *bt.columnShard)\n+\tbt.logger.Println(\"m: \", bt.m)\n+\tbt.logger.Println(\"n: \", bt.n)\n+\tbt.logger.Println(\"k: \", bt.k)\n+\tbt.logger.Println(\"blockId: \", bt.blockId)\n+\tbt.logger.Println(\"sharded T: \", bt.shardedT)\n+\tbt.logger.Println(\"sharded D: \", bt.shardedD)\n+\tbt.logWriter.Flush()\n+\n+\t// Now we have everything initialized.\n+\tgo bt.run()\n }\n \n-// get request of D/T shards from others. Serve with local shard.\n-func (t *bwmfTask) ServeAsParent(fromID uint64, req string, dataReceiver chan<- []byte) {\n-\tvar b []byte\n-\tvar err error\n+type event struct {\n+\tctx      context.Context\n+\tepoch    uint64\n+\tfromID   uint64\n+\trequest  *pb.Request\n+\tretT     chan *pb.DenseMatrixShard\n+\tretD     chan *pb.DenseMatrixShard\n+\tresponse proto.Message\n+\t// to be extended\n+}\n \n-\tgo func() {\n-\t\tif t.epoch%2 == 0 {\n-\t\t\tb, err = json.Marshal(t.dShard)\n+func (bt *bwmfTask) run() {\n+\tfor {\n+\t\tselect {\n+\t\tcase ec := <-bt.epochChange:\n+\t\t\tbt.logger.Println(\"SELECT- Epoch change to \", ec.epoch, \" at task \", bt.taskID)\n+\t\t\tbt.doEnterEpoch(ec.ctx, ec.epoch)\n+\n+\t\tcase reqT := <-bt.getT:\n+\t\t\tbt.logger.Println(\"SELECT- GetT at epoch\", bt.epoch)\n+\t\t\t// XXX: tShard is guaranteed to have been updated here.\n+\t\t\terr := bt.framework.CheckGRPCContext(reqT.ctx)\n \t\t\tif err != nil {\n-\t\t\t\tt.logger.Fatalf(\"Slave can't encode dShard error: %v\\n\", err)\n+\t\t\t\tbt.logger.Panicf(\"GetTShard grpc broken in context at task %d for epoch %d.\", bt.taskID, bt.epoch)\n+\t\t\t\tclose(reqT.retT)\n \t\t\t}\n-\t\t} else {\n-\t\t\tb, err = json.Marshal(t.tShard)\n+\t\t\tbt.logger.Printf(\"To respond with retT at task %d epoch %d.\", bt.taskID, bt.epoch)\n+\t\t\treqT.retT <- bt.shardedT\n+\t\t\tbt.logger.Printf(\"Responded with retT at task %d epoch %d.\", bt.taskID, bt.epoch)\n+\t\tcase reqD := <-bt.getD:\n+\t\t\tbt.logger.Println(\"SELECT- GetD at epoch\", bt.epoch)\n+\t\t\t// XXX: tShard is guaranteed to have been updated here.\n+\n+\t\t\tbt.logger.Println(\"Before checking GRPC status...\")\n+\t\t\tbt.logWriter.Flush()\n+\t\t\terr := bt.framework.CheckGRPCContext(reqD.ctx)","path":"example/bwmf/bwmf_task.go","position":480,"original_position":480,"commit_id":"09a56904fb596acb26896b12c0f1ba5be10e24fd","original_commit_id":"09a56904fb596acb26896b12c0f1ba5be10e24fd","user":{"login":"BaiGang","id":241076,"avatar_url":"https://avatars.githubusercontent.com/u/241076?v=3","gravatar_id":"","url":"https://api.github.com/users/BaiGang","html_url":"https://github.com/BaiGang","followers_url":"https://api.github.com/users/BaiGang/followers","following_url":"https://api.github.com/users/BaiGang/following{/other_user}","gists_url":"https://api.github.com/users/BaiGang/gists{/gist_id}","starred_url":"https://api.github.com/users/BaiGang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BaiGang/subscriptions","organizations_url":"https://api.github.com/users/BaiGang/orgs","repos_url":"https://api.github.com/users/BaiGang/repos","events_url":"https://api.github.com/users/BaiGang/events{/privacy}","received_events_url":"https://api.github.com/users/BaiGang/received_events","type":"User","site_admin":false},"body":"It hangs there forever when the req is handle with a relative bigger latency. @fengjingchao ","created_at":"2015-04-04T11:18:47Z","updated_at":"2015-04-04T11:18:47Z","html_url":"https://github.com/taskgraph/taskgraph/pull/8#discussion_r27768327","pull_request_url":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8","_links":{"self":{"href":"https://api.github.com/repos/taskgraph/taskgraph/pulls/comments/27768327"},"html":{"href":"https://github.com/taskgraph/taskgraph/pull/8#discussion_r27768327"},"pull_request":{"href":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8"}}},"pull_request":{"url":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8","id":29894879,"html_url":"https://github.com/taskgraph/taskgraph/pull/8","diff_url":"https://github.com/taskgraph/taskgraph/pull/8.diff","patch_url":"https://github.com/taskgraph/taskgraph/pull/8.patch","issue_url":"https://api.github.com/repos/taskgraph/taskgraph/issues/8","number":8,"state":"open","locked":false,"title":"[examples/bwfm] WIP - Block-Wise Matrix Factorization.","user":{"login":"BaiGang","id":241076,"avatar_url":"https://avatars.githubusercontent.com/u/241076?v=3","gravatar_id":"","url":"https://api.github.com/users/BaiGang","html_url":"https://github.com/BaiGang","followers_url":"https://api.github.com/users/BaiGang/followers","following_url":"https://api.github.com/users/BaiGang/following{/other_user}","gists_url":"https://api.github.com/users/BaiGang/gists{/gist_id}","starred_url":"https://api.github.com/users/BaiGang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BaiGang/subscriptions","organizations_url":"https://api.github.com/users/BaiGang/orgs","repos_url":"https://api.github.com/users/BaiGang/repos","events_url":"https://api.github.com/users/BaiGang/events{/privacy}","received_events_url":"https://api.github.com/users/BaiGang/received_events","type":"User","site_admin":false},"body":"We will be implementing a distributed matrix factorization program using the fs interface, the task framework and the optimization routines provided by this project. \r\n\r\nBelow is the backlog of this on-going task.\r\n- [x] KL-divergence with smoothness factor.\r\n- [x] Projected gradient methods based optimizer.\r\n- [x] Proto defs of matrix block data and the rpc service.\r\n- [x] Loading sharded row/column matrix using the hdfs interface.\r\n- [x] Preparing `Parameters` using matrix block data.\r\n- [x] Executing the `Optimize` routine.\r\n- [x] Checkpointing of the intermediate data for each epoch.\r\n- [x] Signaling completion of updating T/D via `FlagMeta`. Invoking `IncEpoch` in master.\r\n- [ ] Saving results at the last epoch.\r\n- [ ] NMF test data.\r\n- [ ] Nuisance factors in KL-divergence.","created_at":"2015-02-24T12:58:23Z","updated_at":"2015-04-04T11:18:48Z","closed_at":null,"merged_at":null,"merge_commit_sha":"72bb8fcf141ec61108d949f8d44c33a8261c7b14","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8/commits","review_comments_url":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8/comments","review_comment_url":"https://api.github.com/repos/taskgraph/taskgraph/pulls/comments{/number}","comments_url":"https://api.github.com/repos/taskgraph/taskgraph/issues/8/comments","statuses_url":"https://api.github.com/repos/taskgraph/taskgraph/statuses/09a56904fb596acb26896b12c0f1ba5be10e24fd","head":{"label":"BaiGang:pgmethod","ref":"pgmethod","sha":"09a56904fb596acb26896b12c0f1ba5be10e24fd","user":{"login":"BaiGang","id":241076,"avatar_url":"https://avatars.githubusercontent.com/u/241076?v=3","gravatar_id":"","url":"https://api.github.com/users/BaiGang","html_url":"https://github.com/BaiGang","followers_url":"https://api.github.com/users/BaiGang/followers","following_url":"https://api.github.com/users/BaiGang/following{/other_user}","gists_url":"https://api.github.com/users/BaiGang/gists{/gist_id}","starred_url":"https://api.github.com/users/BaiGang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BaiGang/subscriptions","organizations_url":"https://api.github.com/users/BaiGang/orgs","repos_url":"https://api.github.com/users/BaiGang/repos","events_url":"https://api.github.com/users/BaiGang/events{/privacy}","received_events_url":"https://api.github.com/users/BaiGang/received_events","type":"User","site_admin":false},"repo":{"id":30681702,"name":"taskgraph","full_name":"BaiGang/taskgraph","owner":{"login":"BaiGang","id":241076,"avatar_url":"https://avatars.githubusercontent.com/u/241076?v=3","gravatar_id":"","url":"https://api.github.com/users/BaiGang","html_url":"https://github.com/BaiGang","followers_url":"https://api.github.com/users/BaiGang/followers","following_url":"https://api.github.com/users/BaiGang/following{/other_user}","gists_url":"https://api.github.com/users/BaiGang/gists{/gist_id}","starred_url":"https://api.github.com/users/BaiGang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BaiGang/subscriptions","organizations_url":"https://api.github.com/users/BaiGang/orgs","repos_url":"https://api.github.com/users/BaiGang/repos","events_url":"https://api.github.com/users/BaiGang/events{/privacy}","received_events_url":"https://api.github.com/users/BaiGang/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/BaiGang/taskgraph","description":"A fault tolerant, distributed task driven framework","fork":true,"url":"https://api.github.com/repos/BaiGang/taskgraph","forks_url":"https://api.github.com/repos/BaiGang/taskgraph/forks","keys_url":"https://api.github.com/repos/BaiGang/taskgraph/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BaiGang/taskgraph/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BaiGang/taskgraph/teams","hooks_url":"https://api.github.com/repos/BaiGang/taskgraph/hooks","issue_events_url":"https://api.github.com/repos/BaiGang/taskgraph/issues/events{/number}","events_url":"https://api.github.com/repos/BaiGang/taskgraph/events","assignees_url":"https://api.github.com/repos/BaiGang/taskgraph/assignees{/user}","branches_url":"https://api.github.com/repos/BaiGang/taskgraph/branches{/branch}","tags_url":"https://api.github.com/repos/BaiGang/taskgraph/tags","blobs_url":"https://api.github.com/repos/BaiGang/taskgraph/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BaiGang/taskgraph/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BaiGang/taskgraph/git/refs{/sha}","trees_url":"https://api.github.com/repos/BaiGang/taskgraph/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BaiGang/taskgraph/statuses/{sha}","languages_url":"https://api.github.com/repos/BaiGang/taskgraph/languages","stargazers_url":"https://api.github.com/repos/BaiGang/taskgraph/stargazers","contributors_url":"https://api.github.com/repos/BaiGang/taskgraph/contributors","subscribers_url":"https://api.github.com/repos/BaiGang/taskgraph/subscribers","subscription_url":"https://api.github.com/repos/BaiGang/taskgraph/subscription","commits_url":"https://api.github.com/repos/BaiGang/taskgraph/commits{/sha}","git_commits_url":"https://api.github.com/repos/BaiGang/taskgraph/git/commits{/sha}","comments_url":"https://api.github.com/repos/BaiGang/taskgraph/comments{/number}","issue_comment_url":"https://api.github.com/repos/BaiGang/taskgraph/issues/comments{/number}","contents_url":"https://api.github.com/repos/BaiGang/taskgraph/contents/{+path}","compare_url":"https://api.github.com/repos/BaiGang/taskgraph/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BaiGang/taskgraph/merges","archive_url":"https://api.github.com/repos/BaiGang/taskgraph/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BaiGang/taskgraph/downloads","issues_url":"https://api.github.com/repos/BaiGang/taskgraph/issues{/number}","pulls_url":"https://api.github.com/repos/BaiGang/taskgraph/pulls{/number}","milestones_url":"https://api.github.com/repos/BaiGang/taskgraph/milestones{/number}","notifications_url":"https://api.github.com/repos/BaiGang/taskgraph/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BaiGang/taskgraph/labels{/name}","releases_url":"https://api.github.com/repos/BaiGang/taskgraph/releases{/id}","created_at":"2015-02-12T02:38:37Z","updated_at":"2015-04-03T03:00:47Z","pushed_at":"2015-04-04T11:17:20Z","git_url":"git://github.com/BaiGang/taskgraph.git","ssh_url":"git@github.com:BaiGang/taskgraph.git","clone_url":"https://github.com/BaiGang/taskgraph.git","svn_url":"https://github.com/BaiGang/taskgraph","homepage":null,"size":8766,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"taskgraph:master","ref":"master","sha":"62542465c6bc668685690c5892892db6a4e01c07","user":{"login":"taskgraph","id":9723611,"avatar_url":"https://avatars.githubusercontent.com/u/9723611?v=3","gravatar_id":"","url":"https://api.github.com/users/taskgraph","html_url":"https://github.com/taskgraph","followers_url":"https://api.github.com/users/taskgraph/followers","following_url":"https://api.github.com/users/taskgraph/following{/other_user}","gists_url":"https://api.github.com/users/taskgraph/gists{/gist_id}","starred_url":"https://api.github.com/users/taskgraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/taskgraph/subscriptions","organizations_url":"https://api.github.com/users/taskgraph/orgs","repos_url":"https://api.github.com/users/taskgraph/repos","events_url":"https://api.github.com/users/taskgraph/events{/privacy}","received_events_url":"https://api.github.com/users/taskgraph/received_events","type":"Organization","site_admin":false},"repo":{"id":30477948,"name":"taskgraph","full_name":"taskgraph/taskgraph","owner":{"login":"taskgraph","id":9723611,"avatar_url":"https://avatars.githubusercontent.com/u/9723611?v=3","gravatar_id":"","url":"https://api.github.com/users/taskgraph","html_url":"https://github.com/taskgraph","followers_url":"https://api.github.com/users/taskgraph/followers","following_url":"https://api.github.com/users/taskgraph/following{/other_user}","gists_url":"https://api.github.com/users/taskgraph/gists{/gist_id}","starred_url":"https://api.github.com/users/taskgraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/taskgraph/subscriptions","organizations_url":"https://api.github.com/users/taskgraph/orgs","repos_url":"https://api.github.com/users/taskgraph/repos","events_url":"https://api.github.com/users/taskgraph/events{/privacy}","received_events_url":"https://api.github.com/users/taskgraph/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/taskgraph/taskgraph","description":"A fault tolerant, distributed task driven framework","fork":false,"url":"https://api.github.com/repos/taskgraph/taskgraph","forks_url":"https://api.github.com/repos/taskgraph/taskgraph/forks","keys_url":"https://api.github.com/repos/taskgraph/taskgraph/keys{/key_id}","collaborators_url":"https://api.github.com/repos/taskgraph/taskgraph/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/taskgraph/taskgraph/teams","hooks_url":"https://api.github.com/repos/taskgraph/taskgraph/hooks","issue_events_url":"https://api.github.com/repos/taskgraph/taskgraph/issues/events{/number}","events_url":"https://api.github.com/repos/taskgraph/taskgraph/events","assignees_url":"https://api.github.com/repos/taskgraph/taskgraph/assignees{/user}","branches_url":"https://api.github.com/repos/taskgraph/taskgraph/branches{/branch}","tags_url":"https://api.github.com/repos/taskgraph/taskgraph/tags","blobs_url":"https://api.github.com/repos/taskgraph/taskgraph/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/taskgraph/taskgraph/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/taskgraph/taskgraph/git/refs{/sha}","trees_url":"https://api.github.com/repos/taskgraph/taskgraph/git/trees{/sha}","statuses_url":"https://api.github.com/repos/taskgraph/taskgraph/statuses/{sha}","languages_url":"https://api.github.com/repos/taskgraph/taskgraph/languages","stargazers_url":"https://api.github.com/repos/taskgraph/taskgraph/stargazers","contributors_url":"https://api.github.com/repos/taskgraph/taskgraph/contributors","subscribers_url":"https://api.github.com/repos/taskgraph/taskgraph/subscribers","subscription_url":"https://api.github.com/repos/taskgraph/taskgraph/subscription","commits_url":"https://api.github.com/repos/taskgraph/taskgraph/commits{/sha}","git_commits_url":"https://api.github.com/repos/taskgraph/taskgraph/git/commits{/sha}","comments_url":"https://api.github.com/repos/taskgraph/taskgraph/comments{/number}","issue_comment_url":"https://api.github.com/repos/taskgraph/taskgraph/issues/comments{/number}","contents_url":"https://api.github.com/repos/taskgraph/taskgraph/contents/{+path}","compare_url":"https://api.github.com/repos/taskgraph/taskgraph/compare/{base}...{head}","merges_url":"https://api.github.com/repos/taskgraph/taskgraph/merges","archive_url":"https://api.github.com/repos/taskgraph/taskgraph/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/taskgraph/taskgraph/downloads","issues_url":"https://api.github.com/repos/taskgraph/taskgraph/issues{/number}","pulls_url":"https://api.github.com/repos/taskgraph/taskgraph/pulls{/number}","milestones_url":"https://api.github.com/repos/taskgraph/taskgraph/milestones{/number}","notifications_url":"https://api.github.com/repos/taskgraph/taskgraph/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/taskgraph/taskgraph/labels{/name}","releases_url":"https://api.github.com/repos/taskgraph/taskgraph/releases{/id}","created_at":"2015-02-08T02:19:17Z","updated_at":"2015-04-03T05:35:45Z","pushed_at":"2015-04-04T11:17:20Z","git_url":"git://github.com/taskgraph/taskgraph.git","ssh_url":"git@github.com:taskgraph/taskgraph.git","clone_url":"https://github.com/taskgraph/taskgraph.git","svn_url":"https://github.com/taskgraph/taskgraph","homepage":null,"size":14173,"stargazers_count":27,"watchers_count":27,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":8,"mirror_url":null,"open_issues_count":3,"forks":8,"open_issues":3,"watchers":27,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8"},"html":{"href":"https://github.com/taskgraph/taskgraph/pull/8"},"issue":{"href":"https://api.github.com/repos/taskgraph/taskgraph/issues/8"},"comments":{"href":"https://api.github.com/repos/taskgraph/taskgraph/issues/8/comments"},"review_comments":{"href":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8/comments"},"review_comment":{"href":"https://api.github.com/repos/taskgraph/taskgraph/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/taskgraph/taskgraph/pulls/8/commits"},"statuses":{"href":"https://api.github.com/repos/taskgraph/taskgraph/statuses/09a56904fb596acb26896b12c0f1ba5be10e24fd"}}}}