mirror of https://github.com/grpc/grpc.git
commit
255bbfdbe2
287 changed files with 7435 additions and 3056 deletions
@ -0,0 +1,72 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2015-2016, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#include "src/core/census/grpc_plugin.h" |
||||
|
||||
#include <limits.h> |
||||
|
||||
#include <grpc/census.h> |
||||
|
||||
#include "src/core/census/grpc_filter.h" |
||||
#include "src/core/surface/channel_init.h" |
||||
#include "src/core/channel/channel_stack_builder.h" |
||||
|
||||
static bool maybe_add_census_filter(grpc_channel_stack_builder *builder, |
||||
void *arg_must_be_null) { |
||||
const grpc_channel_args *args = |
||||
grpc_channel_stack_builder_get_channel_arguments(builder); |
||||
if (grpc_channel_args_is_census_enabled(args)) { |
||||
return grpc_channel_stack_builder_prepend_filter( |
||||
builder, &grpc_client_census_filter, NULL, NULL); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
void census_grpc_plugin_init(void) { |
||||
/* Only initialize census if no one else has and some features are
|
||||
* available. */ |
||||
if (census_enabled() == CENSUS_FEATURE_NONE && |
||||
census_supported() != CENSUS_FEATURE_NONE) { |
||||
if (census_initialize(census_supported())) { /* enable all features. */ |
||||
gpr_log(GPR_ERROR, "Could not initialize census."); |
||||
} |
||||
} |
||||
grpc_channel_init_register_stage(GRPC_CLIENT_CHANNEL, INT_MAX, |
||||
maybe_add_census_filter, NULL); |
||||
grpc_channel_init_register_stage(GRPC_CLIENT_UCHANNEL, INT_MAX, |
||||
maybe_add_census_filter, NULL); |
||||
grpc_channel_init_register_stage(GRPC_SERVER_CHANNEL, INT_MAX, |
||||
maybe_add_census_filter, NULL); |
||||
} |
||||
|
||||
void census_grpc_plugin_destroy(void) { census_shutdown(); } |
@ -0,0 +1,259 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2016, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#include "src/core/channel/channel_stack_builder.h" |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
|
||||
int grpc_trace_channel_stack_builder = 0; |
||||
|
||||
typedef struct filter_node { |
||||
struct filter_node *next; |
||||
struct filter_node *prev; |
||||
const grpc_channel_filter *filter; |
||||
grpc_post_filter_create_init_func init; |
||||
void *init_arg; |
||||
} filter_node; |
||||
|
||||
struct grpc_channel_stack_builder { |
||||
// sentinel nodes for filters that have been added
|
||||
filter_node begin; |
||||
filter_node end; |
||||
// various set/get-able parameters
|
||||
const grpc_channel_args *args; |
||||
grpc_transport *transport; |
||||
const char *name; |
||||
}; |
||||
|
||||
struct grpc_channel_stack_builder_iterator { |
||||
grpc_channel_stack_builder *builder; |
||||
filter_node *node; |
||||
}; |
||||
|
||||
grpc_channel_stack_builder *grpc_channel_stack_builder_create(void) { |
||||
grpc_channel_stack_builder *b = gpr_malloc(sizeof(*b)); |
||||
memset(b, 0, sizeof(*b)); |
||||
|
||||
b->begin.filter = NULL; |
||||
b->end.filter = NULL; |
||||
b->begin.next = &b->end; |
||||
b->begin.prev = &b->end; |
||||
b->end.next = &b->begin; |
||||
b->end.prev = &b->begin; |
||||
|
||||
return b; |
||||
} |
||||
|
||||
static grpc_channel_stack_builder_iterator *create_iterator_at_filter_node( |
||||
grpc_channel_stack_builder *builder, filter_node *node) { |
||||
grpc_channel_stack_builder_iterator *it = gpr_malloc(sizeof(*it)); |
||||
it->builder = builder; |
||||
it->node = node; |
||||
return it; |
||||
} |
||||
|
||||
void grpc_channel_stack_builder_iterator_destroy( |
||||
grpc_channel_stack_builder_iterator *it) { |
||||
gpr_free(it); |
||||
} |
||||
|
||||
grpc_channel_stack_builder_iterator * |
||||
grpc_channel_stack_builder_create_iterator_at_first( |
||||
grpc_channel_stack_builder *builder) { |
||||
return create_iterator_at_filter_node(builder, &builder->begin); |
||||
} |
||||
|
||||
grpc_channel_stack_builder_iterator * |
||||
grpc_channel_stack_builder_create_iterator_at_last( |
||||
grpc_channel_stack_builder *builder) { |
||||
return create_iterator_at_filter_node(builder, &builder->end); |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_move_next( |
||||
grpc_channel_stack_builder_iterator *iterator) { |
||||
if (iterator->node == &iterator->builder->end) return false; |
||||
iterator->node = iterator->node->next; |
||||
return true; |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_move_prev( |
||||
grpc_channel_stack_builder_iterator *iterator) { |
||||
if (iterator->node == &iterator->builder->begin) return false; |
||||
iterator->node = iterator->node->prev; |
||||
return true; |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_move_prev( |
||||
grpc_channel_stack_builder_iterator *iterator); |
||||
|
||||
void grpc_channel_stack_builder_set_name(grpc_channel_stack_builder *builder, |
||||
const char *name) { |
||||
GPR_ASSERT(builder->name == NULL); |
||||
builder->name = name; |
||||
} |
||||
|
||||
void grpc_channel_stack_builder_set_channel_arguments( |
||||
grpc_channel_stack_builder *builder, const grpc_channel_args *args) { |
||||
GPR_ASSERT(builder->args == NULL); |
||||
builder->args = args; |
||||
} |
||||
|
||||
void grpc_channel_stack_builder_set_transport( |
||||
grpc_channel_stack_builder *builder, grpc_transport *transport) { |
||||
GPR_ASSERT(builder->transport == NULL); |
||||
builder->transport = transport; |
||||
} |
||||
|
||||
grpc_transport *grpc_channel_stack_builder_get_transport( |
||||
grpc_channel_stack_builder *builder) { |
||||
return builder->transport; |
||||
} |
||||
|
||||
const grpc_channel_args *grpc_channel_stack_builder_get_channel_arguments( |
||||
grpc_channel_stack_builder *builder) { |
||||
return builder->args; |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_append_filter( |
||||
grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, void *user_data) { |
||||
grpc_channel_stack_builder_iterator *it = |
||||
grpc_channel_stack_builder_create_iterator_at_last(builder); |
||||
bool ok = grpc_channel_stack_builder_add_filter_before( |
||||
it, filter, post_init_func, user_data); |
||||
grpc_channel_stack_builder_iterator_destroy(it); |
||||
return ok; |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_prepend_filter( |
||||
grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, void *user_data) { |
||||
grpc_channel_stack_builder_iterator *it = |
||||
grpc_channel_stack_builder_create_iterator_at_first(builder); |
||||
bool ok = grpc_channel_stack_builder_add_filter_after( |
||||
it, filter, post_init_func, user_data); |
||||
grpc_channel_stack_builder_iterator_destroy(it); |
||||
return ok; |
||||
} |
||||
|
||||
static void add_after(filter_node *before, const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, |
||||
void *user_data) { |
||||
filter_node *new = gpr_malloc(sizeof(*new)); |
||||
new->next = before->next; |
||||
new->prev = before; |
||||
new->next->prev = new->prev->next = new; |
||||
new->filter = filter; |
||||
new->init = post_init_func; |
||||
new->init_arg = user_data; |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_add_filter_before( |
||||
grpc_channel_stack_builder_iterator *iterator, |
||||
const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, void *user_data) { |
||||
if (iterator->node == &iterator->builder->begin) return false; |
||||
add_after(iterator->node->prev, filter, post_init_func, user_data); |
||||
return true; |
||||
} |
||||
|
||||
bool grpc_channel_stack_builder_add_filter_after( |
||||
grpc_channel_stack_builder_iterator *iterator, |
||||
const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, void *user_data) { |
||||
if (iterator->node == &iterator->builder->end) return false; |
||||
add_after(iterator->node, filter, post_init_func, user_data); |
||||
return true; |
||||
} |
||||
|
||||
void grpc_channel_stack_builder_destroy(grpc_channel_stack_builder *builder) { |
||||
filter_node *p = builder->begin.next; |
||||
while (p != &builder->end) { |
||||
filter_node *next = p->next; |
||||
gpr_free(p); |
||||
p = next; |
||||
} |
||||
gpr_free(builder); |
||||
} |
||||
|
||||
void *grpc_channel_stack_builder_finish(grpc_exec_ctx *exec_ctx, |
||||
grpc_channel_stack_builder *builder, |
||||
size_t prefix_bytes, int initial_refs, |
||||
grpc_iomgr_cb_func destroy, |
||||
void *destroy_arg) { |
||||
// count the number of filters
|
||||
size_t num_filters = 0; |
||||
for (filter_node *p = builder->begin.next; p != &builder->end; p = p->next) { |
||||
gpr_log(GPR_DEBUG, "%d: %s", num_filters, p->filter->name); |
||||
num_filters++; |
||||
} |
||||
|
||||
// create an array of filters
|
||||
const grpc_channel_filter **filters = |
||||
gpr_malloc(sizeof(*filters) * num_filters); |
||||
size_t i = 0; |
||||
for (filter_node *p = builder->begin.next; p != &builder->end; p = p->next) { |
||||
filters[i++] = p->filter; |
||||
} |
||||
|
||||
// calculate the size of the channel stack
|
||||
size_t channel_stack_size = grpc_channel_stack_size(filters, num_filters); |
||||
|
||||
// allocate memory, with prefix_bytes followed by channel_stack_size
|
||||
char *result = gpr_malloc(prefix_bytes + channel_stack_size); |
||||
// fetch a pointer to the channel stack
|
||||
grpc_channel_stack *channel_stack = |
||||
(grpc_channel_stack *)(result + prefix_bytes); |
||||
// and initialize it
|
||||
grpc_channel_stack_init(exec_ctx, initial_refs, destroy, |
||||
destroy_arg == NULL ? result : destroy_arg, filters, |
||||
num_filters, builder->args, builder->name, |
||||
channel_stack); |
||||
|
||||
// run post-initialization functions
|
||||
i = 0; |
||||
for (filter_node *p = builder->begin.next; p != &builder->end; p = p->next) { |
||||
if (p->init != NULL) { |
||||
p->init(channel_stack, grpc_channel_stack_element(channel_stack, i), |
||||
p->init_arg); |
||||
} |
||||
i++; |
||||
} |
||||
|
||||
grpc_channel_stack_builder_destroy(builder); |
||||
gpr_free((grpc_channel_filter **)filters); |
||||
|
||||
return result; |
||||
} |
@ -0,0 +1,155 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2016, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef GRPC_CORE_CHANNEL_CHANNEL_STACK_BUILDER_H |
||||
#define GRPC_CORE_CHANNEL_CHANNEL_STACK_BUILDER_H |
||||
|
||||
#include <stdbool.h> |
||||
|
||||
#include "src/core/channel/channel_args.h" |
||||
#include "src/core/channel/channel_stack.h" |
||||
|
||||
/// grpc_channel_stack_builder offers a programmatic interface to selected
|
||||
/// and order channel filters
|
||||
typedef struct grpc_channel_stack_builder grpc_channel_stack_builder; |
||||
typedef struct grpc_channel_stack_builder_iterator |
||||
grpc_channel_stack_builder_iterator; |
||||
|
||||
/// Create a new channel stack builder
|
||||
grpc_channel_stack_builder *grpc_channel_stack_builder_create(void); |
||||
|
||||
/// Assign a name to the channel stack: \a name must be statically allocated
|
||||
void grpc_channel_stack_builder_set_name(grpc_channel_stack_builder *builder, |
||||
const char *name); |
||||
|
||||
/// Attach \a transport to the builder (does not take ownership)
|
||||
void grpc_channel_stack_builder_set_transport( |
||||
grpc_channel_stack_builder *builder, grpc_transport *transport); |
||||
|
||||
/// Fetch attached transport
|
||||
grpc_transport *grpc_channel_stack_builder_get_transport( |
||||
grpc_channel_stack_builder *builder); |
||||
|
||||
/// Set channel arguments: \a args must continue to exist until after
|
||||
/// grpc_channel_stack_builder_finish returns
|
||||
void grpc_channel_stack_builder_set_channel_arguments( |
||||
grpc_channel_stack_builder *builder, const grpc_channel_args *args); |
||||
|
||||
/// Return a borrowed pointer to the channel arguments
|
||||
const grpc_channel_args *grpc_channel_stack_builder_get_channel_arguments( |
||||
grpc_channel_stack_builder *builder); |
||||
|
||||
/// Begin iterating over already defined filters in the builder at the beginning
|
||||
grpc_channel_stack_builder_iterator * |
||||
grpc_channel_stack_builder_create_iterator_at_first( |
||||
grpc_channel_stack_builder *builder); |
||||
|
||||
/// Begin iterating over already defined filters in the builder at the end
|
||||
grpc_channel_stack_builder_iterator * |
||||
grpc_channel_stack_builder_create_iterator_at_last( |
||||
grpc_channel_stack_builder *builder); |
||||
|
||||
/// Is an iterator at the first element?
|
||||
bool grpc_channel_stack_builder_iterator_is_first( |
||||
grpc_channel_stack_builder_iterator *iterator); |
||||
|
||||
/// Is an iterator at the end?
|
||||
bool grpc_channel_stack_builder_iterator_is_end( |
||||
grpc_channel_stack_builder_iterator *iterator); |
||||
|
||||
/// Move an iterator to the next item
|
||||
bool grpc_channel_stack_builder_move_next( |
||||
grpc_channel_stack_builder_iterator *iterator); |
||||
|
||||
/// Move an iterator to the previous item
|
||||
bool grpc_channel_stack_builder_move_prev( |
||||
grpc_channel_stack_builder_iterator *iterator); |
||||
|
||||
typedef void (*grpc_post_filter_create_init_func)( |
||||
grpc_channel_stack *channel_stack, grpc_channel_element *elem, void *arg); |
||||
|
||||
/// Add \a filter to the stack, after \a iterator.
|
||||
/// Call \a post_init_func(..., \a user_data) once the channel stack is
|
||||
/// created.
|
||||
bool grpc_channel_stack_builder_add_filter_after( |
||||
grpc_channel_stack_builder_iterator *iterator, |
||||
const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, |
||||
void *user_data) GRPC_MUST_USE_RESULT; |
||||
|
||||
/// Add \a filter to the stack, before \a iterator.
|
||||
/// Call \a post_init_func(..., \a user_data) once the channel stack is
|
||||
/// created.
|
||||
bool grpc_channel_stack_builder_add_filter_before( |
||||
grpc_channel_stack_builder_iterator *iterator, |
||||
const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, |
||||
void *user_data) GRPC_MUST_USE_RESULT; |
||||
|
||||
/// Add \a filter to the beginning of the filter list.
|
||||
/// Call \a post_init_func(..., \a user_data) once the channel stack is
|
||||
/// created.
|
||||
bool grpc_channel_stack_builder_prepend_filter( |
||||
grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, |
||||
void *user_data) GRPC_MUST_USE_RESULT; |
||||
|
||||
/// Add \a filter to the end of the filter list.
|
||||
/// Call \a post_init_func(..., \a user_data) once the channel stack is
|
||||
/// created.
|
||||
bool grpc_channel_stack_builder_append_filter( |
||||
grpc_channel_stack_builder *builder, const grpc_channel_filter *filter, |
||||
grpc_post_filter_create_init_func post_init_func, |
||||
void *user_data) GRPC_MUST_USE_RESULT; |
||||
|
||||
/// Terminate iteration and destroy \a iterator
|
||||
void grpc_channel_stack_builder_iterator_destroy( |
||||
grpc_channel_stack_builder_iterator *iterator); |
||||
|
||||
/// Destroy the builder, return the freshly minted channel stack
|
||||
/// Allocates \a prefix_bytes bytes before the channel stack
|
||||
/// Returns the base pointer of the allocated block
|
||||
/// \a initial_refs, \a destroy, \a destroy_arg are as per
|
||||
/// grpc_channel_stack_init
|
||||
void *grpc_channel_stack_builder_finish(grpc_exec_ctx *exec_ctx, |
||||
grpc_channel_stack_builder *builder, |
||||
size_t prefix_bytes, int initial_refs, |
||||
grpc_iomgr_cb_func destroy, |
||||
void *destroy_arg); |
||||
|
||||
/// Destroy the builder without creating a channel stack
|
||||
void grpc_channel_stack_builder_destroy(grpc_channel_stack_builder *builder); |
||||
|
||||
extern int grpc_trace_channel_stack_builder; |
||||
|
||||
#endif /* GRPC_CORE_CHANNEL_CHANNEL_STACK_BUILDER_H */ |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue